Skip to content

Commit

Permalink
Merge pull request #14442 from opf/implementation/51693-add-second-le…
Browse files Browse the repository at this point in the history
…vel-navigation-for-the-members-page

[51693] Add second level navigation for the members page
  • Loading branch information
ulferts authored Dec 21, 2023
2 parents bb08c79 + a8600a8 commit c48a2c7
Show file tree
Hide file tree
Showing 18 changed files with 473 additions and 14 deletions.
18 changes: 18 additions & 0 deletions app/components/individual_principal_base_filter_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,24 @@ See COPYRIGHT and LICENSE files for more details.
%>
</li>
<% end %>
<% if has_shares? %>
<li class="simple-filters--filter">
<label class='simple-filters--filter-name' for='shared_role_id'><%= t('members.menu.wp_shares') %>:</label>
<%=
select_tag(
:shared_role_id,
options_for_select(
shares,
params[:shared_role_id]
),
{
include_blank: true,
name: "shared_role_id",
class: 'simple-filters--filter-value'
})
%>
</li>
<% end %>
<li class="simple-filters--filter">
<label class='simple-filters--filter-name' for='name'><%= User.human_attribute_name :name %>:</label>
<%= text_field_tag 'name', params[:name], class: 'simple-filters--filter-value' %>
Expand Down
10 changes: 9 additions & 1 deletion app/components/individual_principal_base_filter_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,12 @@ def filter(params)
query(params).results
end

def filter_param_keys
%i(name status group_id role_id)
end

def filtered?(params)
%i(name status group_id role_id).any? { |name| params[name].present? }
filter_param_keys.any? { |name| params[name].present? }
end

def filter_name(query, name)
Expand Down Expand Up @@ -108,6 +112,10 @@ def has_groups?
defined?(groups) && groups.present?
end

def has_shares?
false
end

def params
model
end
Expand Down
60 changes: 58 additions & 2 deletions app/components/members/user_filter_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@

module Members
class UserFilterComponent < ::UserFilterComponent
ALL_SHARED_FILTER_KEY = 'all'

def initially_visible?
false
end
Expand All @@ -38,6 +40,14 @@ def has_close_icon?
true
end

def has_shares?
true
end

def shares
@shares ||= self.class.share_options
end

##
# Adapts the user filter counts to count members as opposed to users.
def extra_user_status_options
Expand All @@ -54,7 +64,7 @@ def extra_user_status_options
def status_members_query(status)
params = {
project_id: project.id,
status:
status:,
}

self.class.filter(params)
Expand All @@ -69,11 +79,57 @@ def base_query
Queries::Members::MemberQuery
end

def filter_param_keys
super + %i(shared_role_id)
end

def share_options
share_options = WorkPackageRole
.where(builtin: builtin_share_roles)
.order(builtin: :asc)
.map { |role| [mapped_shared_role_name(role), role.id] }

share_options.unshift([I18n.t('members.filters.all_shares'), ALL_SHARED_FILTER_KEY])
end

def builtin_share_roles
[
Role::BUILTIN_WORK_PACKAGE_VIEWER,
Role::BUILTIN_WORK_PACKAGE_COMMENTER,
Role::BUILTIN_WORK_PACKAGE_EDITOR
].freeze
end

def mapped_shared_role_name(role)
case role.builtin
when Role::BUILTIN_WORK_PACKAGE_VIEWER
I18n.t('work_package.sharing.permissions.view')
when Role::BUILTIN_WORK_PACKAGE_COMMENTER
I18n.t('work_package.sharing.permissions.comment')
when Role::BUILTIN_WORK_PACKAGE_EDITOR
I18n.t('work_package.sharing.permissions.edit')
else
role.name
end
end

protected

def filter_shares(query, role_id)
if role_id === ALL_SHARED_FILTER_KEY
ids = WorkPackageRole
.where(builtin: builtin_share_roles)
.pluck(:id)

query.where(:role_id, '=', ids.uniq)
elsif role_id.to_i > 0
query.where(:role_id, '=', role_id.to_i)
end
end

def apply_filters(params, query)
super(params, query)
query.where(:only_project_member, '=', 't')
filter_shares(query, params[:shared_role_id]) if params.key?(:shared_role_id)

query
end
Expand Down
52 changes: 52 additions & 0 deletions app/components/open_project/common/submenu_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<div class="op-sidebar">
<div class="op-sidebar--body">
<% top_level_sidebar_menu_items = @sidebar_menu_items.filter { |menu_item| menu_item.header.nil? } %>
<% if top_level_sidebar_menu_items.any? %>
<div class="op-sidemenu">
<ul class="op-sidemenu--items">
<% top_level_sidebar_menu_items.first.children.each do |menu_item| %>
<li class="op-sidemenu--item">
<% selected = menu_item.selected ? 'selected' : '' %>
<a class="op-sidemenu--item-action <%= selected %>" href="<%= menu_item.href %>">
<span class="op-sidemenu--item-title"><%= menu_item.title %></span>
</a>
</li>
<% end %>
</ul>
</div>
<% end %>


<% nested_sidebar_menu_items = @sidebar_menu_items.filter { |menu_item| menu_item.header.present? } %>
<% if nested_sidebar_menu_items.any? %>
<% nested_sidebar_menu_items.each do |menu_item| %>
<div class="op-sidemenu"
data-controller="menus--expandable-sidemenu"
data-application-target="dynamic">

<button class="op-sidemenu--title"
type="button"
data-action="click->menus--expandable-sidemenu#toggleContainer">
<%= menu_item.header %>
<span class="icon-small icon-arrow-up1"
aria-hidden="true"
data-menus--expandable-sidemenu-target="indicator">
</span>
</button>

<ul class="op-sidemenu--items"
data-menus--expandable-sidemenu-target="container">
<% menu_item.children.each do |child_item| %>
<li class="op-sidemenu--item">
<% selected = child_item.selected ? 'selected' : '' %>
<a class="op-sidemenu--item-action <%= selected %>" href="<%= child_item.href %>">
<span class="op-sidemenu--item-title"><%= child_item.title %></span>
</a>
</li>
<% end %>
</ul>
</div>
<% end %>
<% end %>
</div>
</div>
44 changes: 44 additions & 0 deletions app/components/open_project/common/submenu_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

# -- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2023 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
# ++
#
module OpenProject
module Common
class SubmenuComponent < ApplicationComponent
def initialize(sidebar_menu_items: nil)
super()
@sidebar_menu_items = sidebar_menu_items
end

def render?
@sidebar_menu_items.present?
end
end
end
end
109 changes: 109 additions & 0 deletions app/controllers/members/menus_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#-- copyright
# OpenProject is an open source project management software.
# Copyright (C) 2012-2023 the OpenProject GmbH
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License version 3.
#
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
# Copyright (C) 2006-2013 Jean-Philippe Lang
# Copyright (C) 2010-2013 the ChiliProject Team
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# See COPYRIGHT and LICENSE files for more details.
#++
module Members
class MenusController < ApplicationController
before_action :find_project_by_project_id,
:authorize

def show
@sidebar_menu_items = first_level_menu_items + nested_menu_items
render layout: nil
end

private

def first_level_menu_items
[
OpenProject::Menu::MenuGroup.new(header: nil, children: user_status_options)
]
end

def user_status_options
[
OpenProject::Menu::MenuItem.new(title: I18n.t('members.menu.all'),
href: project_members_path,
selected: active_filter_count == 0),
OpenProject::Menu::MenuItem.new(title: I18n.t('members.menu.locked'),
href: project_members_path(status: :locked),
selected: selected?(:status, :locked)),
OpenProject::Menu::MenuItem.new(title: I18n.t('members.menu.invited'),
href: project_members_path(status: :invited),
selected: selected?(:status, :invited))
]
end

def nested_menu_items
[
OpenProject::Menu::MenuGroup.new(header: I18n.t('members.menu.project_roles'), children: project_roles_entries),
OpenProject::Menu::MenuGroup.new(header: I18n.t('members.menu.wp_shares'), children: permission_menu_entries),
OpenProject::Menu::MenuGroup.new(header: I18n.t('members.menu.groups'), children: project_group_entries)
]
end

private

def project_roles_entries
ProjectRole
.where(id: MemberRole.where(member_id: @project.members.select(:id)).select(:role_id))
.distinct
.pluck(:id, :name)
.map { |id, name| menu_item(:role_id, id, name) }
end

def permission_menu_entries
Members::UserFilterComponent
.share_options
.map { |name, id| menu_item(:shared_role_id, id, name) }
end

def project_group_entries
@project
.groups
.order(lastname: :asc)
.distinct
.pluck(:id, :lastname)
.map { |id, name| menu_item(:group_id, id, name) }
end

def menu_item(filter_key, id, name)
OpenProject::Menu::MenuItem.new(title: name,
href: project_members_path(filter_key => id),
selected: selected?(filter_key, id))
end

def selected?(filter_key, value)
return false if active_filter_count > 1

params[filter_key] == value.to_s
end

def active_filter_count
@active_filter_count ||= (params.keys & Members::UserFilterComponent.filter_param_keys.map(&:to_s)).count
end
end
end
4 changes: 3 additions & 1 deletion app/controllers/members_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,14 @@ def members_table_options(roles)

def members_filter_options(roles)
groups = Group.all.sort
shares = WorkPackageRole.all
status = Members::UserFilterComponent.status_param(params)

{
groups:,
roles:,
status:,
shares:,
clear_url: project_members_path(@project),
project: @project
}
Expand Down Expand Up @@ -186,7 +188,7 @@ def possible_members(criteria, limit)
end

def index_members
filters = params.slice(:name, :group_id, :role_id, :status)
filters = params.slice(*Members::UserFilterComponent.filter_param_keys)
filters[:project_id] = @project.id.to_s

@members_query = Members::UserFilterComponent.query(filters)
Expand Down
6 changes: 6 additions & 0 deletions app/views/members/menus/_menu.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<%= turbo_frame_tag "members_menu",
src: project_members_menu_path(@project, params.permit(*Members::UserFilterComponent.filter_param_keys)),
target: '_top',
data: { turbo: false },
loading: :lazy do %>
<% end %>
3 changes: 3 additions & 0 deletions app/views/members/menus/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<%= turbo_frame_tag "members_menu" do %>
<%= render OpenProject::Common::SubmenuComponent.new(sidebar_menu_items: @sidebar_menu_items) %>
<% end %>
6 changes: 6 additions & 0 deletions config/initializers/menus.rb
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,12 @@
before: :settings,
icon: 'group'

menu.push :members_menu,
{ controller: '/members', action: 'index' },
parent: :members,
partial: 'members/menus/menu',
caption: :label_member_plural

menu.push :settings,
{ controller: '/projects/settings/general', action: :show },
caption: :label_project_settings,
Expand Down
Loading

0 comments on commit c48a2c7

Please sign in to comment.