[PATCH conductor] Tasks 3387/3388 UI for user groups implementation

Scott Seago sseago at redhat.com
Mon Jun 25 02:32:39 UTC 2012


On 06/22/2012 12:01 PM, Jozef Zigmund wrote:
> On Tuesday 19 June 2012 17:42:40 Scott Seago wrote:
>> UI for managing local user  groups and for assigning group permissions.
>> When testing, note that after adding/removing a user from a group
>> the user must log out and in again to see the effects.
>> ---
>>   aeolus-conductor.spec.in                           |    1 +
>>   src/app/controllers/application_controller.rb      |    7 +-
>>   src/app/controllers/permissions_controller.rb      |   12 +-
>>   src/app/controllers/user_groups_controller.rb      |  202
>> ++++++++++++++++++++ .../user_groups_helper.rb}                         |
>> 23 +--
>>   src/app/models/common_filter_methods.rb            |    8 +-
>>   src/app/models/derived_permission.rb               |   23 +++
>>   src/app/models/entity.rb                           |   19 ++
>>   src/app/models/permission.rb                       |   23 +++
>>   src/app/models/user_group.rb                       |   12 ++
>>   src/app/views/permissions/_form.html.haml          |   23 ++-
>>   src/app/views/permissions/_permissions.html.haml   |   20 ++
>>   src/app/views/user_groups/_form.html.haml          |   26 +++
>>   src/app/views/user_groups/_list.html.haml          |   41 ++++
>>   src/app/views/user_groups/add_members.html.haml    |   25 +++
>>   src/app/views/user_groups/edit.html.haml           |   14 ++
>>   src/app/views/user_groups/index.html.haml          |    2 +
>>   src/app/views/user_groups/new.html.haml            |   13 ++
>>   src/app/views/user_groups/show.html.haml           |   71 +++++++
>>   src/config/locales/en.yml                          |   62 ++++++-
>>   src/config/routes.rb                               |   15 ++
>>   .../step_definitions/user_group_steps.rb}          |   26 ++-
>>   src/features/support/paths.rb                      |    6 +
>>   src/features/user_group.feature                    |   54 ++++++
>>   .../controllers/user_groups_controller_spec.rb     |   75 +++++++
>>   src/spec/factories/permission.rb                   |    7 +
>>   .../factories/user_group.rb}                       |   23 +--
>>   src/spec/models/permission_spec.rb                 |   19 ++
>>   src/spec/models/user_group_spec.rb                 |   43 ++++
>>   29 files changed, 851 insertions(+), 44 deletions(-)
>>   create mode 100644 src/app/controllers/user_groups_controller.rb
>>   copy src/app/{models/common_filter_methods.rb =>
>> helpers/user_groups_helper.rb} (55%) create mode 100644
>> src/app/views/user_groups/_form.html.haml
>>   create mode 100644 src/app/views/user_groups/_list.html.haml
>>   create mode 100644 src/app/views/user_groups/add_members.html.haml
>>   create mode 100644 src/app/views/user_groups/edit.html.haml
>>   create mode 100644 src/app/views/user_groups/index.html.haml
>>   create mode 100644 src/app/views/user_groups/new.html.haml
>>   create mode 100644 src/app/views/user_groups/show.html.haml
>>   copy src/{app/models/entity.rb =>
>> features/step_definitions/user_group_steps.rb} (52%) create mode 100644
>> src/features/user_group.feature
>>   create mode 100644 src/spec/controllers/user_groups_controller_spec.rb
>>   copy src/{app/models/common_filter_methods.rb =>
>> spec/factories/user_group.rb} (56%) create mode 100644
>> src/spec/models/user_group_spec.rb
>>
>> diff --git a/aeolus-conductor.spec.in b/aeolus-conductor.spec.in
>> index d2c4efd..1ed8159 100644
>> --- a/aeolus-conductor.spec.in
>> +++ b/aeolus-conductor.spec.in
>> @@ -199,6 +199,7 @@ haml="app/views/hardware_profiles
>> app/views/realm_mappings \ app/views/api/hooks \
>>         app/views/api \
>>         app/views/images \
>> +      app/views/user_groups \
>>         app/views/api/builds app/views/api/provider_images \
>>         app/views/api/target_images app/views/api/entrypoint \
>>         app/views/api/environments \
>> diff --git a/src/app/controllers/application_controller.rb
>> b/src/app/controllers/application_controller.rb index eb5931b..b30326b
>> 100644
>> --- a/src/app/controllers/application_controller.rb
>> +++ b/src/app/controllers/application_controller.rb
>> @@ -281,7 +281,7 @@ class ApplicationController<  ActionController::Base
>>
>>     def set_admin_users_tabs(tab)
>>       @tabs = [{:name =>  t('application_controller.admin_tabs.users'), :url
>> =>  users_url, :id =>  'users'}, -             #{:name =>
>> t('application_controller.admin_tabs.groups'), :url =>  groups_url, :id =>
>> 'groups'}, +             {:name =>
>> t('application_controller.admin_tabs.user_groups'), :url =>
>> user_groups_url, :id =>  'user_groups'}, {:name =>
>> t('application_controller.admin_tabs.permissions'), :url =>
>> permissions_url, :id =>  'permissions'}, ]
>>       unless @details_tab = @tabs.find {|t| t[:id] == tab}
>> @@ -350,7 +350,10 @@ class ApplicationController<  ActionController::Base
>>       else
>>         local_perms = @permission_object.permissions
>>       end
>> -    @permissions = paginate_collection(local_perms, params[:page])
>> +    @permissions = paginate_collection(local_perms.
>> +      apply_filters(:preset_filter_id =>
>> params[:permissions_preset_filter], +                    :search_filter =>
>> params[:permissions_search]), +
>> params[:page])
>>
>>       @permission_list_header = []
>>       unless (@show_inherited or @show_global)
>> diff --git a/src/app/controllers/permissions_controller.rb
>> b/src/app/controllers/permissions_controller.rb index acdbb2c..6d8ae54
>> 100644
>> --- a/src/app/controllers/permissions_controller.rb
>> +++ b/src/app/controllers/permissions_controller.rb
>> @@ -165,11 +165,21 @@ class PermissionsController<  ApplicationController
>>
>>                   :permission_object_id =>  p.permission_object_id
>>
>>     end
>>
>> +  def filter
>> +    redirect_to_original({"permissions_preset_filter" =>
>> params[:permissions_preset_filter], "permissions_search" =>
>> params[:permissions_search]}) +  end
>> +
>> +  def filter_entities
>> +    redirect_to_original({"entities_preset_filter" =>
>> params[:entities_preset_filter], "entities_search" =>
>> params[:entities_search]}) +  end
>> +
>>     private
>>
>>     def load_entities
>>       sort_order = params[:sort_by].nil? ? "name" : params[:sort_by]
>> -    @entities = paginate_collection(Entity.all(:order =>  sort_order),
>> params[:page]) +    @entities =
>> paginate_collection(Entity.order(sort_order).
>> +      apply_filters(:preset_filter_id =>  params[:entities_preset_filter],
>> +                    :search_filter =>  params[:entities_search]),
>> params[:page]) end
>>
>>     def load_headers
>> diff --git a/src/app/controllers/user_groups_controller.rb
>> b/src/app/controllers/user_groups_controller.rb new file mode 100644
>> index 0000000..189ec09
>> --- /dev/null
>> +++ b/src/app/controllers/user_groups_controller.rb
>> @@ -0,0 +1,202 @@
>> +#
>> +#   Copyright 2011 Red Hat, Inc.
>> +#
>> +#   Licensed under the Apache License, Version 2.0 (the "License");
>> +#   you may not use this file except in compliance with the License.
>> +#   You may obtain a copy of the License at
>> +#
>> +#       http://www.apache.org/licenses/LICENSE-2.0
>> +#
>> +#   Unless required by applicable law or agreed to in writing, software
>> +#   distributed under the License is distributed on an "AS IS" BASIS,
>> +#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
>> implied. +#   See the License for the specific language governing
>> permissions and +#   limitations under the License.
>> +#
>> +
>> +class UserGroupsController<  ApplicationController
>> +  before_filter :require_user, :except =>  [:new, :create]
>> +
>> +  def index
>> +    require_privilege(Privilege::VIEW, User)
>> +    @title = t'user_groups.groups'
>> +    clear_breadcrumbs
>> +    save_breadcrumb(user_groups_path)
>> +    set_admin_users_tabs 'user_groups'
>> +    @params = params
>> +    load_headers
>> +    load_user_groups
>> +    respond_to do |format|
>> +      format.html
>> +      format.js { render :partial =>  'list' }
>> +    end
>> +  end
>> +
>> +  def show
>> +    @user_group = UserGroup.find(params[:id])
>> +    require_privilege(Privilege::VIEW, User)
>> +    @title = @user_group.name
>> +    save_breadcrumb(user_group_path(@user_group), @user_group.name)
>> +    @tab_captions = ['Properties']
>> +    @details_tab = params[:details_tab].blank? ? 'properties' :
>> params[:details_tab] +    @members =
>> paginate_collection(@user_group.members, params[:page]) +    respond_to do
>> |format|
>> +      format.html
>> +      format.js do
>> +        if params.delete :details_pane
>> +          render :partial =>  'layouts/details_pane' and return
>> +        end
>> +        render :partial =>  @details_tab
>> +      end
>> +    end
>> +  end
>> +
>> +  def new
>> +    require_privilege(Privilege::CREATE, User)
>> +    @title = t'user_groups.new.new_user_group'
>> +    @user_group = UserGroup.new
>> +    # remove when we enable ldap
>> +    @user_group.membership_source = UserGroup::MEMBERSHIP_SOURCE_LOCAL
>> +  end
>> +
>> +  def create
>> +    if params[:commit] == "Reset"
>> +      redirect_to :action =>  'new' and return
>> +    end
>> +
>> +    require_privilege(Privilege::MODIFY, User)
>> +    @user_group = UserGroup.new(params[:user_group])
>> +    unless @user_group.save
>> +      render :action =>  'new' and return
>> +    end
>> +
>> +    respond_to do |format|
>> +      if @user_group.save
>> +        flash[:notice] = t "user_groups.flash.notice.added"
>> +        format.html { redirect_to user_groups_path }
>> +        format.json { render :json =>  @user_group, :status =>  :created }
>> +      else
>> +        flash.now[:warning] = t "user_groups.flash.warning.creation_failed"
>> +        format.html { render :new }
>> +        format.js { render :partial =>  'new' }
>> +        format.json { render :json =>  @user_group.errors, :status =>
>> :unprocessable_entity } +      end
>> +    end
>> +  end
>> +
>> +  def edit
>> +    @user_group = UserGroup.find(params[:id])
>> +    require_privilege(Privilege::MODIFY, User)
>> +    @title = t'user_groups.edit.edit_user_group'
>> +  end
>> +
>> +  def update
>> +    @user_group = UserGroup.find(params[:id])
>> +    require_privilege(Privilege::MODIFY, User)
>> +
>> +    if params[:commit] == "Reset"
>> +      redirect_to edit_user_group_url(@user_group) and return
>> +    end
>> +
>> +    redirect_to root_url and return unless @user_group
>> +
>> +    unless @user_group.update_attributes(params[:user_group])
>> +      render :action =>  'edit' and return
>> +    else
>> +      flash[:notice] = t"user_groups.flash.notice.updated"
>> +      redirect_to user_group_path(@user_group)
>> +    end
>> +  end
>> +
>> +  def multi_destroy
>> +    require_privilege(Privilege::MODIFY, User)
>> +    deleted_user_groups = []
>> +
>> +    begin
>> +      UserGroup.transaction do
>> +        UserGroup.find(params[:user_group_selected]).each do |user_group|
>> +          if user_group.destroy
>> +            deleted_user_groups<<  user_group.name
>> +          end
>> +        end
>> +      end
>> +
>> +      unless deleted_user_groups.empty?
>> +        flash[:notice] =  "#{t('user_groups.flash.notice.more_deleted',
>> :count =>  deleted_user_groups.length)} #{deleted_user_groups.join(', ')}" +
>>       end
>> +
>> +    rescue =>  ex
>> +      flash[:warning] = t('user_groups.flash.warning.not_delete', :reason
>> =>  ex.message) +    end
>> +
>> +    redirect_to user_groups_url
>> +  end
>> +
>> +  def destroy
>> +    require_privilege(Privilege::MODIFY, User)
>> +    user_group = UserGroup.find(params[:id])
>> +    if user_group.destroy
>> +      flash[:notice] = t"user_groups.flash.notice.deleted"
>> +    else
>> +      flash[:notice] = t"user_groups.flash.warning.delete_failed"
>> +    end
>> +
>> +    respond_to do |format|
>> +      format.html { redirect_to user_groups_path }
>> +    end
>> +  end
>> +
>> +  def add_members
>> +    @user_group = UserGroup.find(params[:id])
>> +    require_privilege(Privilege::MODIFY, User)
>> +
>> +    unless @user_group.membership_source ==
>> UserGroup::MEMBERSHIP_SOURCE_LOCAL +      flash[:error] =
>> t('user_groups.flash.error.not_a_local_group') +      redirect_to
>> user_group_path(@user_group) and return
>> +    end
>> +
>> +    @users = paginate_collection(User.where('users.id not in (?)',
>> +                                            @user_group.members.empty? ?
>> +                                            0 :
>> @user_group.members.map(&:id)), +
>> params[:page])
>> +
>> +    added = []
>> +    not_added = []
>> +
>> +    if params[:members_selected].blank?
>> +      flash[:error] = t"user_groups.flash.error.select_to_add_members" if
>> request.post? +    else
>> +      User.find(params[:members_selected]).each do |member|
>> +        if !@user_group.members.include?(member) and
>> +            @user_group.members<<  member
>> +          added<<  member.name
>> +        else
>> +          not_added<<  member.name
>> +        end
>> +      end
>> +      unless added.empty?
>> +        flash[:notice] = "#{t('user_groups.flash.notice.members_added')}:
>> #{added.join(', ')}" +      end
>> +      unless not_added.empty?
>> +        flash[:error] = "#{t('user_groups.flash.error.members_not_added')}:
>> #{not_added.join(', ')}" +      end
>> +      respond_to do |format|
>> +        format.html { redirect_to user_group_path(@user_group) }
>> +      end
>> +    end
>> +  end
>> +
>> +  def load_user_groups
>> +    sort_order = params[:sort_by].nil? ? "name" : params[:sort_by]
>> +    @user_groups = UserGroup.apply_filters(:preset_filter_id =>
>> params[:users_preset_filter], :search_filter =>
>> params[:user_groups_search]).order(sort_order) +  end
>> +
>> +  def load_headers
>> +    @header = [
>> +      { :name =>  'checkbox', :class =>  'checkbox', :sortable =>  false },
>> +      { :name =>  t('user_groups.index.name'), :sortable =>  false },
>> +      { :name =>  t('user_groups.index.type'), :sortable =>  false },
>> +    ]
>> +  end
>> +
>> +end
>> diff --git a/src/app/models/common_filter_methods.rb
>> b/src/app/helpers/user_groups_helper.rb similarity index 55%
>> copy from src/app/models/common_filter_methods.rb
>> copy to src/app/helpers/user_groups_helper.rb
>> index 7bb2be2..85ad81f 100644
>> --- a/src/app/models/common_filter_methods.rb
>> +++ b/src/app/helpers/user_groups_helper.rb
>> @@ -1,5 +1,5 @@
>>   #
>> -#   Copyright 2011 Red Hat, Inc.
>> +#   Copyright 2012 Red Hat, Inc.
>>   #
>>   #   Licensed under the Apache License, Version 2.0 (the "License");
>>   #   you may not use this file except in compliance with the License.
>> @@ -13,19 +13,12 @@
>>   #   See the License for the specific language governing permissions and
>>   #   limitations under the License.
>>   #
>> -#this module contains functions for filtering table data
>> -module CommonFilterMethods
>> -  def apply_filters(options = {})
>> -
>> apply_preset_filter(options[:preset_filter_id]).apply_search_filter(options
>> [:search_filter]) -  end
>> -
>> -  private
>> -
>> -  def apply_preset_filter(preset_filter_id)
>> -    if preset_filter_id.present?
>> -      self::PRESET_FILTERS_OPTIONS.select{|item| item[:id] ==
>> preset_filter_id}.first[:query] -    else
>> -      scoped
>> -    end
>> +module UserGroupsHelper
>> +  def members_header
>> +    [
>> +      { :name =>  'checkbox', :class =>  'checkbox', :sortable =>  false },
>> +      { :name =>  t('users.index.username'), :sortable =>  false },
>> +      { :name =>  t('user_groups.index.name'), :sortable =>  false },
>> +    ]
>>     end
>>   end
>> diff --git a/src/app/models/common_filter_methods.rb
>> b/src/app/models/common_filter_methods.rb index 7bb2be2..9491ecb 100644
>> --- a/src/app/models/common_filter_methods.rb
>> +++ b/src/app/models/common_filter_methods.rb
>> @@ -23,7 +23,13 @@ module CommonFilterMethods
>>
>>     def apply_preset_filter(preset_filter_id)
>>       if preset_filter_id.present?
>> -      self::PRESET_FILTERS_OPTIONS.select{|item| item[:id] ==
>> preset_filter_id}.first[:query] +      option =
>> self::PRESET_FILTERS_OPTIONS.select { |item|
>> +        item[:id] == preset_filter_id}.first
>> +      if option[:query]
>> +        option[:query]
>> +      else
>> +        includes(option[:includes]).where(option[:where])
>> +      end
>>       else
>>         scoped
>>       end
>> diff --git a/src/app/models/derived_permission.rb
>> b/src/app/models/derived_permission.rb index b7a2530..80d055d 100644
>> --- a/src/app/models/derived_permission.rb
>> +++ b/src/app/models/derived_permission.rb
>> @@ -15,6 +15,9 @@
>>   #
>>
>>   class DerivedPermission<  ActiveRecord::Base
>> +  class<<  self
>> +    include CommonFilterMethods
>> +  end
>>     # the source permission for the denormalized object
>>     belongs_to :permission
>>     validates_presence_of :permission_id
>> @@ -54,4 +57,24 @@ class DerivedPermission<  ActiveRecord::Base
>>     validates_uniqueness_of :permission_id, :scope =>  [:permission_object_id,
>>                                                        :permission_object_typ
>>                                                        :e]
>>
>> +  # :query is handled differently for permission
>> +  PRESET_FILTERS_OPTIONS = [
>> +    {:title =>  "permissions.preset_filters.user_permissions",
>> +     :id =>  "user_permissions",
>> +     :includes =>  :entity,
>> +     :where =>  {"entities.entity_target_type" =>  "User"}},
>> +    {:title =>  "permissions.preset_filters.group_permissions",
>> +     :id =>  "group_permissions",
>> +     :includes =>  :entity,
>> +     :where =>  {"entities.entity_target_type" =>  "UserGroup"}}
>> +  ]
>> +
>> +  def self.apply_search_filter(search)
>> +    if search
>> +      includes("entity").where("lower(entities.name) LIKE :search", :search
>> =>  "%#{search.downcase}%") +    else
>> +      scoped
>> +    end
>> +  end
>> +
>>   end
>> diff --git a/src/app/models/entity.rb b/src/app/models/entity.rb
>> index bbc56fe..db88a5e 100644
>> --- a/src/app/models/entity.rb
>> +++ b/src/app/models/entity.rb
>> @@ -15,6 +15,9 @@
>>   #
>>
>>   class Entity<  ActiveRecord::Base
>> +  class<<  self
>> +    include CommonFilterMethods
>> +  end
>>     belongs_to :entity_target, :polymorphic =>  true
>>     validates_presence_of :entity_target_id
>>     has_many :session_entities, :dependent =>  :destroy
>> @@ -26,4 +29,20 @@ class Entity<  ActiveRecord::Base
>>     belongs_to :user_group, :class_name =>  "UserGroup",
>>
>>                             :foreign_key =>  "entity_target_id"
>>
>> +  PRESET_FILTERS_OPTIONS = [
>> +    {:title =>  "user",
>> +     :id =>  "users",
>> +     :where =>  {"entities.entity_target_type" =>  "User"}},
>> +    {:title =>  "user_group",
>> +     :id =>  "user_groups",
>> +     :where =>  {"entities.entity_target_type" =>  "UserGroup"}}
>> +  ]
>> +
>> +  def self.apply_search_filter(search)
>> +    if search
>> +      where("lower(entities.name) LIKE :search", :search =>
>> "%#{search.downcase}%") +    else
>> +      scoped
>> +    end
>> +  end
>>   end
>> diff --git a/src/app/models/permission.rb b/src/app/models/permission.rb
>> index 048f7a5..9b4c0ac 100644
>> --- a/src/app/models/permission.rb
>> +++ b/src/app/models/permission.rb
>> @@ -30,6 +30,9 @@
>>   #
>>
>>   class Permission<  ActiveRecord::Base
>> +  class<<  self
>> +    include CommonFilterMethods
>> +  end
>>     belongs_to :role
>>     belongs_to :entity
>>
>> @@ -67,6 +70,17 @@ class Permission<  ActiveRecord::Base
>>
>>     after_save :update_derived_permissions
>>
>> +  # :query is handled differently for permission
>> +  PRESET_FILTERS_OPTIONS = [
>> +    {:title =>  "permissions.preset_filters.user_permissions",
>> +     :id =>  "user_permissions",
>> +     :includes =>  :entity,
>> +     :where =>  {"entities.entity_target_type" =>  "User"}},
>> +    {:title =>  "permissions.preset_filters.group_permissions",
>> +     :id =>  "group_permissions",
>> +     :includes =>  :entity,
>> +     :where =>  {"entities.entity_target_type" =>  "UserGroup"}}
>> +  ]
>>     def user
>>       entity.user
>>     end
>> @@ -100,4 +114,13 @@ class Permission<  ActiveRecord::Base
>>         end
>>       end
>>     end
>> +
>> +  def self.apply_search_filter(search)
>> +    if search
>> +      includes("entity").where("lower(entities.name) LIKE :search", :search
>> =>  "%#{search.downcase}%") +    else
>> +      scoped
>> +    end
>> +  end
>> +
>>   end
>> diff --git a/src/app/models/user_group.rb b/src/app/models/user_group.rb
>> index 293d0db..dcb2f63 100644
>> --- a/src/app/models/user_group.rb
>> +++ b/src/app/models/user_group.rb
>> @@ -15,6 +15,9 @@
>>   #
>>
>>   class UserGroup<  ActiveRecord::Base
>> +  class<<  self
>> +    include CommonFilterMethods
>> +  end
>>     # name will correspond to the group name if we're using LDAP, otherwise
>> it's # entered by the admin creating the group
>>
>> @@ -42,4 +45,13 @@ class UserGroup<  ActiveRecord::Base
>>       self.entity.name = "#{self.name} (#{self.membership_source})"
>>       self.entity.save!
>>     end
>> +
>> +  def self.apply_search_filter(search)
>> +    if search
>> +      where("lower(name) LIKE :search", :search =>  "%#{search.downcase}%")
>> +    else
>> +      scoped
>> +    end
>> +  end
>> +
>>   end
>> diff --git a/src/app/views/permissions/_form.html.haml
>> b/src/app/views/permissions/_form.html.haml index 95aa05d..39b1865 100644
>> --- a/src/app/views/permissions/_form.html.haml
>> +++ b/src/app/views/permissions/_form.html.haml
>> @@ -1,3 +1,23 @@
>> +- content_for :entities_filter_controls do
>> +  %li
>> +    = label_tag :entities_preset_filter, t('filter_table.viewing')
>> +    = select_tag(:entities_preset_filter,
>> preset_filters_options_for_select(Entity::PRESET_FILTERS_OPTIONS,
>> params[:entities_preset_filter]), :include_blank =>
>> t("permissions.preset_filters.all_entities"), :disabled =>  false) +    =
>> hidden_field_tag :current_path, request.fullpath
>> +    = restful_submit_tag t("filter_table.apply_filters"), "filter",
>> filter_entities_permissions_path, 'POST', :class =>  'button', :id =>
>> 'apply_entities_preset_filter' +    %span.label.badge.dark= @entities.count
>> +  %li.table-search-filter
>> +    = text_field_tag :entities_search, params[:entities_search],
>> :placeholder =>  t("filter_table.search") +    = restful_submit_tag "",
>> "search", filter_entities_permissions_path, 'POST', :class =>  'button', :id
>> =>  'apply_entities_search' +
>> +  :javascript
>> +    $(document).ready(function(){
>> +      $("#apply_entities_preset_filter").hide();
>> +      $("#entities_preset_filter").change(function() {
>> +        $("#apply_entities_preset_filter").click();
>> +      });
>> +      Conductor.clickOnEnterKeypress($('#entities_search'),
>> $('#apply_entities_search')); +    });
>> +
>>   - content_for :form_footer do
>>     = hidden_field_tag :permission_object_type, @permission_object.class.name
>> = hidden_field_tag :permission_object_id, @permission_object.id @@ -5,7
>> +25,8 @@
>>     = hidden_field_tag :use_tabs, @use_tabs
>>     = link_to t('cancel'), @return_path, :class =>  'button danger'
>>     = restful_submit_tag t('permissions.form.grant_access'), "create",
>> permissions_path, 'POST', :id =>  'save_button', :class =>  'button' -=
>> filter_table(@header, @entities) do |entity|
>> += filter_table(@header, @entities,
>> +               :filter_controls =>  :entities_filter_controls) do |entity|
>>     %tr{:class =>  cycle('nostripe','stripe')}
>>       %td
>>         -# - selected = params[:select] == 'all'
>> diff --git a/src/app/views/permissions/_permissions.html.haml
>> b/src/app/views/permissions/_permissions.html.haml index b1e18f7..56a0c8e
>> 100644
>> --- a/src/app/views/permissions/_permissions.html.haml
>> +++ b/src/app/views/permissions/_permissions.html.haml
>> @@ -24,6 +24,26 @@
>>           %li= link_to t('permissions.inherited_access'),
>> params.merge(:show_inherited =>  true, :show_global =>  false, :page =>  1), {
>> :class =>  'button primary', :id =>  'inherited_permission_button'} %li=
>> link_to t('permissions.global_access'), params.merge(:show_inherited =>
>> false, :show_global =>  true, :page =>  1), { :class =>  'button primary', :id
>> =>  'global_permission_button'}
>>
>> +- content_for :permissions_filter_controls do
>> +  %li
>> +    = label_tag :permissions_preset_filter, t('filter_table.viewing')
>> +    = select_tag(:permissions_preset_filter,
>> preset_filters_options_for_select(Permission::PRESET_FILTERS_OPTIONS,
>> params[:permissions_preset_filter]), :include_blank =>
>> t("permissions.preset_filters.all_permissions"), :disabled =>  false) +    =
>> hidden_field_tag :current_path, request.fullpath
>> +    = restful_submit_tag t("filter_table.apply_filters"), "filter",
>> filter_permissions_path, 'POST', :class =>  'button', :id =>
>> 'apply_permissions_preset_filter' +    %span.label.badge.dark=
>> @permissions.count
>> +  %li.table-search-filter
>> +    = text_field_tag :permissions_search, params[:permissions_search],
>> :placeholder =>  t("filter_table.search") +    = restful_submit_tag "",
>> "search", filter_permissions_path, 'POST', :class =>  'button', :id =>
>> 'apply_permissions_search' +
>> +  :javascript
>> +    $(document).ready(function(){
>> +      $("#apply_permissions_preset_filter").hide();
>> +      $("#permissions_preset_filter").change(function() {
>> +        $("#apply_permissions_preset_filter").click();
>> +      });
>> +      Conductor.clickOnEnterKeypress($('#permissions_search'),
>> $('#apply_permissions_search')); +    });
>> +
>>   = filter_table(@permission_list_header,
>>                  @permissions,
>>
>>                  :form_header =>  :permissions_form_header,
>>
>> diff --git a/src/app/views/user_groups/_form.html.haml
>> b/src/app/views/user_groups/_form.html.haml new file mode 100644
>> index 0000000..1db6a7a
>> --- /dev/null
>> +++ b/src/app/views/user_groups/_form.html.haml
>> @@ -0,0 +1,26 @@
>> +- if @user_group.errors.any?
>> +  = render 'layouts/error_messages', :object =>  @user_group
>> +
>> +%fieldset
>> +  .field
>> +    = form.label :type
>> +    .input
>> +      - if @user_group.new_record?
>> +        = form.select(:membership_source,
>> UserGroup::MEMBERSHIP_SOURCES.collect {|x| [t("user_groups.#{x.downcase}"),
>> x]}, {}, {:disabled =>  true}) +        = form.hidden_field
>> :membership_source
>> +      - else
>> +        = t("user_groups.#{@user_group.membership_source.downcase}")
>> +  .field
>> +    = form.label :name
>> +    .input
>> +      - if @user_group.new_record? or @user_group.membership_source ==
>> UserGroup::MEMBERSHIP_SOURCE_LOCAL +        = form.text_field :name
>> +      - else
>> +        = @user_group.name
>> +  .field
>> +    = form.label :description
>> +    .input
>> +      = form.text_area :description, :class =>  'long'
>> +
>> +%fieldset.options
>> +  = form.submit "#{t'user_groups.form.save_user_group'}",  :class =>
>> "submit button pill" diff --git a/src/app/views/user_groups/_list.html.haml
>> b/src/app/views/user_groups/_list.html.haml new file mode 100644
>> index 0000000..5c99b39
>> --- /dev/null
>> +++ b/src/app/views/user_groups/_list.html.haml
>> @@ -0,0 +1,41 @@
>> +%header
>> +  %h2#user_groups.groups= @title
>> +
>> +- content_for :form_header do
>> +  %li= restful_submit_tag "#{t'user_groups.list.delete_selected'}",
>> "destroy", multi_destroy_user_groups_path, 'DELETE', :id =>
>> 'delete_button', :class =>  'button danger' +  %li= link_to
>> t("user_groups.list.add_user_group"), new_user_group_path, :class =>
>> 'button', :id =>  'add_user_group_button' +
>> +- content_for :filter_controls do
>> +  %li
>> +    = label_tag :user_groups_preset_filter, t('filter_table.viewing')
>> +    = hidden_field_tag :current_path, request.fullpath
>> +    = select_tag(:user_groups_preset_filter,
>> preset_filters_options_for_select(User::PRESET_FILTERS_OPTIONS,
>> params[:user_groups_preset_filter]), :include_blank =>
>> t("user_groups.preset_filters.all_user_groups"), :disabled =>  true) +    =
>> restful_submit_tag t("filter_table.apply_filters"), "index",
>> filter_user_groups_path, 'POST', :class =>  'button', :id =>
>> 'apply_user_groups_preset_filter' +    %span.label.badge.dark=
>> @user_groups.count
>> +  /  = link_to "Filter", "#", :class =>  'button pill'
>> +  /%li
>> +  /%li.more_actions
>> +  /  %span= image_tag "button-userdrop.png"
>> +  /  %ul
>> +  /    %li= link_to "Add/Remove Columns", "#"
>> +  /    %li= link_to "Freeze Column", "#"
>> +  %li.table-search-filter
>> +    = text_field_tag :user_groups_search, params[:user_groups_search],
>> :placeholder =>  t("filter_table.search") +    = restful_submit_tag "",
>> "index", filter_user_groups_path, 'POST', :class =>  'button', :id =>
>> 'apply_user_groups_search' +
>> +  :javascript
>> +    $(document).ready(function(){
>> +      $("#apply_user_groups_preset_filter").hide();
>> +      $("#user_groups_preset_filter").change(function() {
>> +        $("#apply_user_groups_preset_filter").click();
>> +      });
>> +      Conductor.clickOnEnterKeypress($('#user_groups_search'),
>> $('#apply_user_groups_search')); +    });
>> +
>> += filter_table(@header, @user_groups) do |user_group|
>> +  %tr{:class =>  cycle('nostripe','stripe')}
>> +    %td{:class =>  'checkbox'}
>> +      - selected = params[:select] == 'all'
>> +      = check_box_tag "user_group_selected[]", user_group.id, selected, :id
>> =>  "user_group_checkbox_#{user_group.id}" +    %td= link_to
>> user_group.name, user_group_path(user_group)
>> +    %td= t("user_groups.#{user_group.membership_source.downcase}")
>> diff --git a/src/app/views/user_groups/add_members.html.haml
>> b/src/app/views/user_groups/add_members.html.haml new file mode 100644
>> index 0000000..4b3a30c
>> --- /dev/null
>> +++ b/src/app/views/user_groups/add_members.html.haml
>> @@ -0,0 +1,25 @@
>> += render :partial =>  'layouts/admin_nav'
>> +%header.page-header
>> +  .obj_actions
>> +    .return_to
>> +      = t'return_to'
>> +      = link_to @user_group.name, user_group_path(@user_group), :id =>
>> 'user_group_button' +  %h1.user_groups= @user_group.name
>> +
>> +%section.content-section.user_groups
>> +  %header
>> +    .align-center
>> +      %strong= t("user_groups.choose_members")
>> +  .content
>> +    - content_for :form_footer do
>> +      = link_to t('cancel'), user_group_path(@user_group), :class =>
>> 'button danger' +      = restful_submit_tag
>> t('user_groups.show.add_members'), "add",
>> add_members_user_group_path(@user_group), 'POST', :id =>  'save_button',
>> :class =>  'button' +    = filter_table(members_header, @users) do |user|
>> +      %tr{:class =>  cycle('nostripe','stripe')}
>> +        %td{:class =>  'checkbox'}
>> +          - selected = params[:select] == 'all'
>> +          = check_box_tag "members_selected[]", user.id, selected, :id =>
>> "member_checkbox_#{user.id}" +        %td
>> +          = link_to user.login, users_path(user)
>> +        %td
>> +          = user.name
>> diff --git a/src/app/views/user_groups/edit.html.haml
>> b/src/app/views/user_groups/edit.html.haml new file mode 100644
>> index 0000000..7350a43
>> --- /dev/null
>> +++ b/src/app/views/user_groups/edit.html.haml
>> @@ -0,0 +1,14 @@
>> += render :partial =>  'layouts/admin_nav'
>> +%header.page-header
>> +  .obj_actions
>> +    .return_to
>> +      =t'return_to'
>> +      = link_to "#{t'user_groups.groups'}", user_groups_path
>> +  %h1.users= @title
>> +
>> +%section.content-section.user_groups
>> +  .content
>> +    .align-center
>> +    = @user_group.name
>> +    = form_for @user_group, :url =>  user_group_path(@user_group), :html =>
>> {:class =>  'generic horizontal'} do |f| +      = render :partial =>  "form",
>> :locals =>  { :form =>  f }
>> diff --git a/src/app/views/user_groups/index.html.haml
>> b/src/app/views/user_groups/index.html.haml new file mode 100644
>> index 0000000..7cc23db
>> --- /dev/null
>> +++ b/src/app/views/user_groups/index.html.haml
>> @@ -0,0 +1,2 @@
>> += render :partial =>  'layouts/admin_nav'
>> += render :partial =>  'layouts/admin_users_tabs'
>> diff --git a/src/app/views/user_groups/new.html.haml
>> b/src/app/views/user_groups/new.html.haml new file mode 100644
>> index 0000000..eaaf3a7
>> --- /dev/null
>> +++ b/src/app/views/user_groups/new.html.haml
>> @@ -0,0 +1,13 @@
>> += render :partial =>  'layouts/admin_nav'
>> +%header.page-header
>> +  .obj_actions
>> +    .return_to
>> +      =t'return_to'
>> +      = link_to "#{t'user_groups.groups'}", user_groups_path
>> +  %h1.users= @title
>> +
>> +%section.content-section.user_groups
>> +  .content
>> +    .align-center
>> +    = form_for @user_group, :url =>  user_groups_path, :html =>  {:class =>
>> 'generic horizontal'} do |f| +      = render :partial =>  "form", :locals =>
>> { :form =>  f }
>> diff --git a/src/app/views/user_groups/show.html.haml
>> b/src/app/views/user_groups/show.html.haml new file mode 100644
>> index 0000000..426137f
>> --- /dev/null
>> +++ b/src/app/views/user_groups/show.html.haml
>> @@ -0,0 +1,71 @@
>> += render :partial =>  'layouts/admin_nav'
>> +%header.page-header
>> +  .obj_actions
>> +    .return_to
>> +      = t'return_to'
>> +      = link_to t("user_groups.groups"), user_groups_path
>> +    - if check_privilege(Privilege::CREATE, User)
>> +      = link_to t('user_groups.new.new_user_group'), new_user_group_url,
>> :class =>  'button primary', :id =>  'new_user_group_button' +
>> .button-group
>> +      - if check_privilege(Privilege::MODIFY, User)
>> +        = link_to t('edit'), edit_user_group_path(@user_group), :class =>
>> 'button pill', :id =>  'edit_button' +        = button_to t("delete"),
>> user_group_path(@user_group), :method =>  'delete', :confirm =>  "Are you
>> sure you want to delete?", :class =>  'button pill danger', :id =>  'delete'
>> +  %h1.no-icon= @title
>> +
>> +%section.content-section.user
>> +  %header
>> +    %h2=t'properties'
>> +
>> +  .content
>> +    %table.properties_table
>> +      %tbody
>> +        %tr
>> +          %td= t('user_groups.show.type')
>> +          %td= @user_group.membership_source
>> +        %tr
>> +          %td= t('user_groups.show.name')
>> +          %td= @user_group.name
>> +        %tr
>> +          %td= t('user_groups.show.description')
>> +          %td= @user_group.description
>> +
>> +- if @user_group.membership_source == UserGroup::MEMBERSHIP_SOURCE_LOCAL
>> +  %section.content-section
>> +    %header
>> +      %h2=t('user_groups.show.members')
>> +
>> +    .content
>> +      - content_for :form_header do
>> +        - if check_privilege(Privilege::CREATE, Deployable)
>> +          %li= restful_submit_tag t("remove"), "destroy",
>> remove_members_user_group_path(@user_group), 'DELETE', :id =>
>> 'delete_button', :class =>  'button danger' +          %li= link_to
>> t("user_groups.show.add_members"),
>> add_members_user_group_path(@user_group), :class =>  'button', :id =>
>> 'add_members_button' +
>> +      - content_for :filter_controls do
>> +        %li
>> +          = label_tag :members_preset_filter, t('filter_table.viewing')
>> +          = hidden_field_tag :current_path, request.fullpath
>> +          = select_tag(:members_preset_filter,
>> preset_filters_options_for_select(Deployable::PRESET_FILTERS_OPTIONS,
>> params[:members_preset_filter]), :include_blank =>
>> t("users.preset_filters.all_users"), :disabled =>  true) +          =
>> restful_submit_tag t("filter_table.apply_filters"), "filter",
>> filter_members_user_group_path(@user_group), 'POST', :class =>  'button',
>> :id =>  'apply_members_preset_filter' +          %span.label.badge.dark=
>> @user_group.members.count
>> +        %li.table-search-filter
>> +          = text_field_tag :members_search, params[:members_search],
>> :placeholder =>  t("filter_table.search") +          = restful_submit_tag
>> "", "search", filter_members_user_group_path(@user_group), 'POST', :class
>> =>  'button', :id =>  'apply_members_search' +
>> +        :javascript
>> +          $(document).ready(function(){
>> +            $("#apply_members_preset_filter").hide();
>> +            $("#members_preset_filter").change(function() {
>> +              $("#apply_members_preset_filter").click();
>> +            });
>> +            Conductor.clickOnEnterKeypress($('#members_search'),
>> $('#apply_members_search')); +          });
>> +
>> +      = filter_table(members_header, @members) do |member|
>> +        %tr{:class =>  cycle('nostripe','stripe')}
>> +          %td{:class =>  'checkbox'}
>> +            - selected = params[:select] == 'all'
>> +            = check_box_tag "members_selected[]", member.id, selected, :id
>> =>  "member_checkbox_#{member.id}" +          %td
>> +            = link_to member.login, users_path(member)
>> +          %td
>> +            = member.name
>> diff --git a/src/config/locales/en.yml b/src/config/locales/en.yml
>> index 8c2e010..19b825a 100644
>> --- a/src/config/locales/en.yml
>> +++ b/src/config/locales/en.yml
>> @@ -102,6 +102,62 @@ en:
>>         name_starts_with_B: "Name starts with B"
>>       errors:
>>         has_running_instances: '%{login} has running instances'
>> +  user_groups:
>> +    groups: "User Groups"
>> +    local: Local
>> +    ldap: LDAP
>> +    return_to: "Return to:"
>> +    confirm_delete: "Are you sure you want to delete this user group?"
>> +    quick_jump: "Quick Jump:"
>> +    choose_members: "Choose Users to add to this Group."
>> +    index:
>> +      name: Name
>> +      type: Type
>> +    show:
>> +      name: Name
>> +      type: Type
>> +      description: Description
>> +      members: Members
>> +      add_members: Add Members
>> +      edit: Edit
>> +      delete: Delete
>> +      created: CREATED
>> +      last_updated: LAST UPDATED
>> +    form:
>> +      name: Name
>> +      type: Type
>> +      save_user_group: Save User Group
>> +    new:
>> +      new_user_group: New User Group
>> +      neccessary_information: "Enter necessary information about your new
>> user group and click 'Save User Group' when you are finished." +    list:
>> +      delete_selected: Delete Selected
>> +      add_user_group: Add User Group
>> +    edit:
>> +      edit_user_group: Edit User Group
>> +    flash:
>> +      notice:
>> +        added: "User Group added"
>> +        updated: "User Group updated!"
>> +        deleted: "User Group has been successfully deleted."
>> +        more_deleted:
>> +          one: Deleted user group
>> +          other: Deleted user groups
>> +        members_added: "These users have been added"
>> +        members_removed: "These users have been removed"
>> +      warning:
>> +        creation_failed: 'Could not create user group'
>> +        delete_failed: 'Could not delete user group'
>> +        not_delete: 'Cannot delete: %{reason}'
>> +      error:
>> +        members_not_added: "Could not add these users"
>> +        members_not_removed: "Could not remove these users"
>> +        not_a_local_group: 'Only locally-managed groups can be managed
>> here.' +        select_to_add_members: 'You must select at least one user
>> to add.' +    preset_filters:
>> +      all_user_groups: "All User Groups"
>> +      local: "Locally-managed User Groups"
>> +      ldap: "LDAP-managed User Groups"
>>     user_sessions:
>>       new:
>>         username: "Username:"
>> @@ -151,6 +207,7 @@ en:
>>     role: Role
>>     role_assignments: Role Assignments
>>     user: User
>> +  user_group: User Group
>>     privileges: Privileges
>>     general_settings: General Settings
>>     new_user: New User
>> @@ -1285,7 +1342,10 @@ en:
>>           user_and_role: "%{user} (%{role})"
>>           user_and_role_change: "%{user} (from %{old_role} to %{role})"
>>       preset_filters:
>> +      all_entities: All Entities
>>         all_permissions: All Permissions
>> +      user_permissions: User Permissions
>> +      group_permissions: Group Permissions
>>     quotas:
>>       quota: Quota
>>       edit:
>> @@ -1353,7 +1413,7 @@ en:
>>         realms: Realms
>>         hardware: Hardware
>>         users: Users
>> -      groups: Groups
>> +      user_groups: User Groups
>>         permissions: Global Role Grants
>>         pool_families: Pool Families
>>         images: Images
>> diff --git a/src/config/routes.rb b/src/config/routes.rb
>> index da762a7..38fbb53 100644
>> --- a/src/config/routes.rb
>> +++ b/src/config/routes.rb
>> @@ -83,6 +83,8 @@ Conductor::Application.routes.draw do
>>         get :list
>>         delete :multi_destroy
>>         post :multi_update
>> +      post :filter
>> +      post :filter_entities
>>       end
>>     end
>>
>> @@ -156,6 +158,19 @@ Conductor::Application.routes.draw do
>>       post :filter, :on =>  :collection
>>     end
>>
>> +  resources :user_groups do
>> +    collection do
>> +      delete 'multi_destroy'
>> +      post :filter
>> +    end
>> +    member do
>> +      get 'add_members'
>> +      post 'add_members'
>> +      post 'remove_members'
>> +      post 'filter_members'
>> +    end
>> +  end
>> +
>>     resources :logs do
>>       collection do
>>         post 'filter'
>> diff --git a/src/app/models/entity.rb
>> b/src/features/step_definitions/user_group_steps.rb similarity index 52%
>> copy from src/app/models/entity.rb
>> copy to src/features/step_definitions/user_group_steps.rb
>> index bbc56fe..b5426f2 100644
>> --- a/src/app/models/entity.rb
>> +++ b/src/features/step_definitions/user_group_steps.rb
>> @@ -13,17 +13,23 @@
>>   #   See the License for the specific language governing permissions and
>>   #   limitations under the License.
>>   #
>> +Given /^a user group "([^"]*)" exists$/ do |name|
>> +  unless UserGroup.find_by_name(name)
>> +    FactoryGirl.create :user_group, :name =>  name
>> +  end
>> +end
>>
>> -class Entity<  ActiveRecord::Base
>> -  belongs_to :entity_target, :polymorphic =>  true
>> -  validates_presence_of :entity_target_id
>> -  has_many :session_entities, :dependent =>  :destroy
>> -  has_many :permissions, :dependent =>  :destroy
>> -  has_many :derived_permissions, :dependent =>  :destroy
>> +Given /^there is a user group "([^"]*)"$/ do |name|
>> +  unless UserGroup.find_by_name(name)
>> +    FactoryGirl.create :user_group, :name =>  name
>> +  end
>> +end
>>
>> -  # type-specific associations
>> -  belongs_to :user, :class_name =>  "User", :foreign_key =>
>> "entity_target_id" -  belongs_to :user_group, :class_name =>  "UserGroup",
>> -                          :foreign_key =>  "entity_target_id"
>> +Then /^there should be (\d+) user groups?$/ do |number|
>> +  UserGroup.count.should == number.to_i
>> +end
>>
>> +When /^I check "([^"]*)" user group$/ do |user_group_name|
>> +  user_group = UserGroup.find_by_name(user_group_name)
>> +  check("user_group_checkbox_#{user_group.id}")
>>   end
>> diff --git a/src/features/support/paths.rb b/src/features/support/paths.rb
>> index 38f926d..f809a8c 100644
>> --- a/src/features/support/paths.rb
>> +++ b/src/features/support/paths.rb
>> @@ -35,6 +35,9 @@ module NavigationHelpers
>>       when /^(.*)'s user page$/i
>>          user_path(User.find_by_login($1))
>>
>> +    when /^(.*)'s user group page$/i
>> +       user_group_path(UserGroup.find_by_name($1))
>> +
>>       when /^(.*)'s role page$/i
>>          role_path(Role.find_by_name($1))
>>
>> @@ -179,6 +182,9 @@ module NavigationHelpers
>>       when /^the (.*)'s edit user page$/
>>         edit_user_path(User.find_by_login($1))
>>
>> +    when /^the (.*)'s edit user group page$/
>> +      edit_user_group_path(UserGroup.find_by_name($1))
>> +
>>       when /^the "(.*)" catalog page/
>>         url_for catalog_path(Catalog.find_by_name($1))
>>
>> diff --git a/src/features/user_group.feature
>> b/src/features/user_group.feature new file mode 100644
>> index 0000000..d858035
>> --- /dev/null
>> +++ b/src/features/user_group.feature
>> @@ -0,0 +1,54 @@
>> +Feature: Manage User Groups
>> +  In order to manage user groups
>> +  As an admin
>> +  I want to add/edit/remove user groupss
>> +
>> +  Background:
>> +    Given I am an authorised user
>> +    And I am logged in
>> +    And a user group "testgroup" exists
>> +
>> +  Scenario: Show user group detials
>> +    Given I am on the user groups page
>> +    And there is a user group "testgroup"
>> +    When I follow "testgroup"
>> +    Then I should be on testgroup's user group page
>> +
>> +  Scenario: Delete user groups
>> +    Given there is a user group "testgroup"
>> +    Given there is a user group "testgroup2"
>> +    And I am on the user groups page
>> +    Then there should be 2 user groups
>> +    And I should be on the user groups page
>> +    When I check "testgroup" user group
>> +    And I press "Delete"
>> +    Then I should see "Deleted user group"
>> +    And there should be 1 user group
>> +
>> +  Scenario: Create new user
>> +    Given I am on the user groups page
>> +    When I follow "add_user_group_button"
>> +    Then I should be on the new user group page
>> +    And I should see "New User Group"
>> +    When I fill in the following:
>> +      | Name  | testgroup3             |
>> +    And I press "Save"
>> +    Then I should be on the user groups page
>> +    And I should see "User Group added"
>> +
>> +  Scenario: Edit existing user group
>> +    Given I am on the user groups page
>> +    And I follow "testgroup"
>> +    Then I should be on testgroup's user group page
>> +    And I should see "testgroup"
>> +    When I follow "Edit"
>> +    Then I should be on the testgroup's edit user group page
>> +    And I fill in "user_group_name" with "newname"
>> +    When I press "Save"
>> +    Then I should be on newname's user group page
>> +    And I should see "User Group updated"
>> +    And I should see "newname"
>> +
>> +
>> +
>> +
> remove this ^, because git said "src/features/user_group.feature:51: new blank
> line at EOF" when I applied this patch
>
>> diff --git a/src/spec/controllers/user_groups_controller_spec.rb
>> b/src/spec/controllers/user_groups_controller_spec.rb new file mode 100644
>> index 0000000..d5e68bb
>> --- /dev/null
>> +++ b/src/spec/controllers/user_groups_controller_spec.rb
>> @@ -0,0 +1,75 @@
>> +#
>> +#   Copyright 2011 Red Hat, Inc.
>> +#
>> +#   Licensed under the Apache License, Version 2.0 (the "License");
>> +#   you may not use this file except in compliance with the License.
>> +#   You may obtain a copy of the License at
>> +#
>> +#       http://www.apache.org/licenses/LICENSE-2.0
>> +#
>> +#   Unless required by applicable law or agreed to in writing, software
>> +#   distributed under the License is distributed on an "AS IS" BASIS,
>> +#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
>> implied. +#   See the License for the specific language governing
>> permissions and +#   limitations under the License.
>> +#
>> +
>> +require 'spec_helper'
>> +
>> +describe UserGroupsController do
>> +
>> +  fixtures :all
>> +  before(:each) do
>> +    @user_group = Factory.create(:user_group)
>> +    @tuser = FactoryGirl.create :tuser
>> +    @admin_permission = FactoryGirl.create :admin_permission
>> +    @admin = @admin_permission.user
>> +  end
>> +
>> +  describe "#create" do
>> +    context "user enters valid input" do
>> +      it "creates user group" do
>> +        mock_warden(@admin)
>> +        lambda do
>> +          post :create, :user_group =>  {
>> +            :name =>  "user_group2", :membership_source =>  "local" }
>> +        end.should change(UserGroup, :count).by(1)
>> +
>> +        response.should redirect_to(user_groups_path)
>> +      end
>> +
>> +      it "fails to create user" do
>> +        mock_warden(@admin)
>> +        lambda do
>> +          post :create, :user_group =>  {}
>> +        end.should_not change(UserGroup, :count)
>> +
>> +        returned_user_group = assigns[:user_group]
>> +        returned_user_group.errors.empty?.should be_false
>> +        returned_user_group.should have(1).errors_on(:name)
>> +        returned_user_group.should have(2).errors_on(:membership_source)
>> +
>> +        response.should render_template('new')
>> +      end
>> +    end
>> +  end
>> +
>> +  it "allows an admin to create user group" do
>> +    mock_warden(@admin)
>> +    lambda do
>> +      post :create, :user_group =>  {
>> +           :name =>  "user_group3", :membership_source =>  "local" }
>> +    end.should change(UserGroup, :count)
>> +
>> +    response.should redirect_to(user_groups_url)
>> +  end
>> +
>> +  it "should not allow a regular user to create user group" do
>> +    mock_warden(@tuser)
>> +    lambda do
>> +      post :create, :user_group =>  {
>> +           :name =>  "user_group4", :membership_source =>  "local" }
>> +    end.should_not change(UserGroup, :count)
>> +  end
>> +
>> +end
>> diff --git a/src/spec/factories/permission.rb
>> b/src/spec/factories/permission.rb index e401fe8..4b2d2b4 100644
>> --- a/src/spec/factories/permission.rb
>> +++ b/src/spec/factories/permission.rb
>> @@ -62,4 +62,11 @@ FactoryGirl.define do
>>       entity { |r| FactoryGirl.create(:pool_family_user).entity }
>>     end
>>
>> +  factory :group_admin_permission, :parent =>  :permission do
>> +    role { |r| Role.first(:conditions =>  ['name = ?', 'base.admin']) ||
>> FactoryGirl.create(:role, :name =>  'base.admin') } +    permission_object {
>> |r| BasePermissionObject.general_permission_scope } +    entity { |r|
>> FactoryGirl.create(:user_group).entity }
>> +  end
>> +
>> +
>>   end
>> diff --git a/src/app/models/common_filter_methods.rb
>> b/src/spec/factories/user_group.rb similarity index 56%
>> copy from src/app/models/common_filter_methods.rb
>> copy to src/spec/factories/user_group.rb
>> index 7bb2be2..bf1f6fc 100644
>> --- a/src/app/models/common_filter_methods.rb
>> +++ b/src/spec/factories/user_group.rb
>> @@ -1,5 +1,5 @@
>>   #
>> -#   Copyright 2011 Red Hat, Inc.
>> +#   Copyright 2012 Red Hat, Inc.
>>   #
>>   #   Licensed under the Apache License, Version 2.0 (the "License");
>>   #   you may not use this file except in compliance with the License.
>> @@ -13,19 +13,16 @@
>>   #   See the License for the specific language governing permissions and
>>   #   limitations under the License.
>>   #
>> -#this module contains functions for filtering table data
>> -module CommonFilterMethods
>> -  def apply_filters(options = {})
>> -
>> apply_preset_filter(options[:preset_filter_id]).apply_search_filter(options
>> [:search_filter]) -  end
>>
>> -  private
>> +FactoryGirl.define do
>> +
>> +  factory :user_group do |u|
>> +    sequence(:name) { |n| "group#{n}" }
>> +    membership_source 'local'
>> +  end
>>
>> -  def apply_preset_filter(preset_filter_id)
>> -    if preset_filter_id.present?
>> -      self::PRESET_FILTERS_OPTIONS.select{|item| item[:id] ==
>> preset_filter_id}.first[:query] -    else
>> -      scoped
>> -    end
>> +  factory :ldap_group , :parent =>  :user_group do
>> +    membership_source = 'LDAP'
>>     end
>> +
>>   end
>> diff --git a/src/spec/models/permission_spec.rb
>> b/src/spec/models/permission_spec.rb index 7f05587..ea9a20d 100644
>> --- a/src/spec/models/permission_spec.rb
>> +++ b/src/spec/models/permission_spec.rb
>> @@ -84,4 +84,23 @@ describe Permission do
>>         should be_false
>>     end
>>
>> +  it "User added to Admin group should be able to create users" do
>> +    newuser = FactoryGirl.create(:user)
>> +    group_admin_permission = FactoryGirl.create(:group_admin_permission)
>> +    user_group = group_admin_permission.user_group
>> +    SessionEntity.update_session(@session_id, newuser)
>> +
>> BasePermissionObject.general_permission_scope.has_privilege(@session_id, +
>>                                                                newuser, +
>>
>> Privilege::CREATE, +
>>          User).should be_false +    user_group.members<<  newuser
>> +    newuser.reload
>> +    SessionEntity.update_session(@session_id, newuser)
>> +
>> BasePermissionObject.general_permission_scope.has_privilege(@session_id, +
>>                                                                newuser, +
>>
>> Privilege::CREATE, +
>>          User).should be_true +
>> +  end
>> +
>>   end
>> diff --git a/src/spec/models/user_group_spec.rb
>> b/src/spec/models/user_group_spec.rb new file mode 100644
>> index 0000000..915df0a
>> --- /dev/null
>> +++ b/src/spec/models/user_group_spec.rb
>> @@ -0,0 +1,43 @@
>> +#
>> +#   Copyright 2012 Red Hat, Inc.
>> +#
>> +#   Licensed under the Apache License, Version 2.0 (the "License");
>> +#   you may not use this file except in compliance with the License.
>> +#   You may obtain a copy of the License at
>> +#
>> +#       http://www.apache.org/licenses/LICENSE-2.0
>> +#
>> +#   Unless required by applicable law or agreed to in writing, software
>> +#   distributed under the License is distributed on an "AS IS" BASIS,
>> +#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
>> implied. +#   See the License for the specific language governing
>> permissions and +#   limitations under the License.
>> +#
>> +
>> +require 'spec_helper'
>> +
>> +describe UserGroup do
>> +  before(:each) do
>> +  end
>> +
>> +  it "should create a new local group" do
>> +    user_group = Factory.create(:user_group)
>> +    user_group.should be_valid
>> +    user_group.members.size.should == 0
>> +  end
>> +
>> +  it "should create a new ldap group" do
>> +    user_group = Factory.create(:ldap_group)
>> +    user_group.should be_valid
>> +  end
>> +
>> +  it "should add members to a new local group" do
>> +    user_group = Factory.create(:user_group)
>> +    user = Factory.create(:tuser)
>> +    user_group.members.size.should == 0
>> +    user_group.members<<  user
>> +    user_group.reload
>> +    user_group.members.size.should == 1
>> +  end
>> +
>> +end
> NACK with one inline nit.
>
> I'd like to advise you for next time to split patch to smaller parts(patches),
> then the reviewing will be more comfortable. Also whole new funcionality could
> be more readable.
>
> It's good approach to have only 1-3 changed files per patch, in this patch is
> 29 files changed, 851 insertions(+), 44 deletions(-)
>
> Issues to fix:
> - navigation.rb - need to be added user_groups to "second_level.item :users
> ... , :highlights_on =>  .."
> - method remove_members is missing in user_groups_controller.rb  (what caused
> that member cannot be removed from group)
> - missing test for removing members
>
I've fixed the mentioned issues above (including the whitespace one), 
and I'm about to send an updated patch.

Scott

>




More information about the aeolus-devel mailing list