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

Tzu-Mainn Chen tzumainn at redhat.com
Mon Jul 2 16:15:11 UTC 2012


ACK; fixes the issues pointed out by previous review.

Mainn

----- Original Message -----
> From: "Scott Seago" <sseago at redhat.com>
> To: aeolus-devel at lists.fedorahosted.org
> Cc: "Scott Seago" <sseago at redhat.com>
> Sent: Monday, July 2, 2012 11:05:57 AM
> Subject: [PATCH conductor] Tasks 3387/3388 UI for user groups implementation	(revised)
> 
> 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      |   13 +-
>  src/app/controllers/user_groups_controller.rb      |  238
>  ++++++++++++++++++++
>  .../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                          |   63 +++++-
>  src/config/navigation.rb                           |    2 +-
>  src/config/routes.rb                               |   15 ++
>  src/features/step_definitions/user_group_steps.rb  |   56 +++++
>  src/features/support/paths.rb                      |    6 +
>  src/features/user_group.feature                    |   72 ++++++
>  .../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 ++++
>  30 files changed, 948 insertions(+), 35 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
>  create mode 100644 src/features/step_definitions/user_group_steps.rb
>  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 6186651..30e6f43 100644
> --- a/aeolus-conductor.spec.in
> +++ b/aeolus-conductor.spec.in
> @@ -209,6 +209,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 1907d23..d5da863 100644
> --- a/src/app/controllers/application_controller.rb
> +++ b/src/app/controllers/application_controller.rb
> @@ -282,7 +282,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}
> @@ -352,7 +352,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 c4d1371..41cad43 100644
> --- a/src/app/controllers/permissions_controller.rb
> +++ b/src/app/controllers/permissions_controller.rb
> @@ -165,11 +165,22 @@ 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_column(Entity, sort_order)),
> +    @entities = paginate_collection(Entity.
> +      all(:order => sort_column(Entity, sort_order)).
> +      apply_filters(:preset_filter_id =>
> params[:entities_preset_filter],
> +                    :search_filter => params[:entities_search]),
>                                      params[:page])
>    end
>  
> diff --git a/src/app/controllers/user_groups_controller.rb
> b/src/app/controllers/user_groups_controller.rb
> new file mode 100644
> index 0000000..59b8665
> --- /dev/null
> +++ b/src/app/controllers/user_groups_controller.rb
> @@ -0,0 +1,238 @@
> +#
> +#   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 remove_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
> +
> +    removed=[]
> +    not_removed=[]
> +
> +    if params[:members_selected].blank?
> +      if request.post?
> +        flash[:error] =
> t"user_groups.flash.error.select_to_remove_members"
> +      end
> +    else
> +      User.find(params[:members_selected]).each do |member|
> +        if @user_group.members.delete member
> +          removed << member.name
> +        else
> +          not_removed << member.name
> +        end
> +      end
> +      unless removed.empty?
> +        flash[:notice] =
> "#{t('user_groups.flash.notice.members_removed')}: #{removed.join(',
> ')}"
> +      end
> +      unless not_removed.empty?
> +        flash[:error] =
> "#{t('user_groups.flash.error.members_not_removed')}:
> #{not_removed.join(', ')}"
> +      end
> +    end
> +    respond_to do |format|
> +      format.html { redirect_to user_group_path(@user_group) }
> +    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_type]
>  
> +  # :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..9295373 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:
> @@ -1333,6 +1393,7 @@ en:
>        administer: Administer
>      second_level:
>        users: Users
> +      user_groups: User Groups
>        environments: Environments
>        content: Content
>        cloud_providers: Cloud Providers
> @@ -1353,7 +1414,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/navigation.rb b/src/config/navigation.rb
> index c8a48a2..4c37e23 100644
> --- a/src/config/navigation.rb
> +++ b/src/config/navigation.rb
> @@ -20,7 +20,7 @@ SimpleNavigation::Configuration.run do |navigation|
>      first_level.dom_class = 'container'
>      first_level.item :monitor, t('navigation.first_level.monitor'),
>      pools_path, :class => 'monitor', :link => { :id => 'monitor' },
>      :highlights_on =>
>      /\/deployments|\/pools|\/instances|\/logs|\/\z/
>      first_level.item :administer,
>      t('navigation.first_level.administer'), users_path, :class =>
>      'administer' do |second_level|
> -      second_level.item :users, t('navigation.second_level.users'),
> users_path, :link => { :class => 'users' }, :highlights_on =>
> /\/users|\/roles|\/permissions|\/account/
> +      second_level.item :users, t('navigation.second_level.users'),
> users_path, :link => { :class => 'users' }, :highlights_on =>
> /\/users|\/user_groups|\/roles|\/permissions|\/account/
>        second_level.item :environments,
>        t('navigation.second_level.environments'),
>        pool_families_path, :link => { :class => 'environments' },
>        :highlights_on => /\/pool_families|\/images/
>        second_level.item :content,
>        t('navigation.second_level.content'), catalogs_path, :link =>
>        { :class => 'content' }, :highlights_on =>
>        /\/catalogs|\/catalog_entries|\/realms|\/hardware_profiles|\/realm_mappings|\/deployables/
>        second_level.item :cloud_providers,
>        t('navigation.second_level.cloud_providers'), providers_path,
>        :link => { :class => 'providers' }, :highlights_on =>
>        /\/providers|\/provider_realms|\/config_servers/
> diff --git a/src/config/routes.rb b/src/config/routes.rb
> index da762a7..68ed728 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'
> +      delete 'remove_members'
> +      post 'filter_members'
> +    end
> +  end
> +
>    resources :logs do
>      collection do
>        post 'filter'
> diff --git a/src/features/step_definitions/user_group_steps.rb
> b/src/features/step_definitions/user_group_steps.rb
> new file mode 100644
> index 0000000..2959a21
> --- /dev/null
> +++ b/src/features/step_definitions/user_group_steps.rb
> @@ -0,0 +1,56 @@
> +#
> +#   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.
> +#
> +Given /^a user group "([^"]*)" exists$/ do |name|
> +  unless UserGroup.find_by_name(name)
> +    FactoryGirl.create :user_group, :name => name
> +  end
> +end
> +
> +Given /^there is a user group "([^"]*)"$/ do |name|
> +  unless UserGroup.find_by_name(name)
> +    FactoryGirl.create :user_group, :name => name
> +  end
> +end
> +
> +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
> +
> +When /^I check the "([^"]*)" member$/ do |login|
> +  member = User.find_by_login(login)
> +  check("member_checkbox_#{member.id}")
> +end
> +
> +Then /^there should be (\d+) user belonging to "([^"]*)"$/ do
> |count, name|
> +  user_group = UserGroup.find_by_name(name)
> +  user_group.members.count == count
> +end
> +
> +Given /^there is a user "([^"]*)" belonging to user group
> "([^"]*)"$/ do |login, group_name|
> +  member = User.find_by_login(login)
> +  user_group = UserGroup.find_by_name(group_name)
> +  user_group.members << member
> +end
> +
> +Then /^there should not exist a member belonging to "([^"]*)"$/ do
> |group_name|
> +  user_group = UserGroup.find_by_name(group_name)
> +  user_group.members.count == 0
> +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..08f4db7
> --- /dev/null
> +++ b/src/features/user_group.feature
> @@ -0,0 +1,72 @@
> +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
> +    And a user "testuser" 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 group
> +    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"
> +
> +  Scenario: Add member to 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 "Add Member"
> +    And I check the "testuser" member
> +    And I press "Add Member"
> +    Then there should be 1 user belonging to "testgroup"
> +    And I should see "testuser"
> +
> +  Scenario: Remove member from user group
> +    And there is a user "testuser" belonging to user group
> "testgroup"
> +    Given I am on the user groups page
> +    And I follow "testgroup"
> +    Then I should be on testgroup's user group page
> +    Then I should see "testuser"
> +    When I check the "testuser" member
> +    And I press "Remove"
> +    Then there should not exist a member belonging to "testgroup"
> 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
> --
> 1.7.6.5
> 
> 



More information about the aeolus-devel mailing list