Skip to content

Commit

Permalink
[ENHC0010038] Components/groups search and sort (#656)
Browse files Browse the repository at this point in the history
* Create template table component files

* Incorporate template into the groups table

* Change the Groups tab to use the new table component

* Create search box

* Add starting search functionality

* Remove debug code

* Refactor code to utilize table_component.rb

* Modify sorting to exclude the Access Granted column

* Add ability to search by group name

* Remove html tag that causes test failures

* Add starting test for groups search

* Add table component to create and destroy turbo streams

* Add tests for searching groups

* Modify Ransack to use the proper attributes

* Add abilities variable to reduce helpers calls

* Create tests for column sorting

* Cleanup debug code and comments

* Create tests for the table component

* Clean up code in table component tests

* Convert group group links to use the table component

* Fix failed tests caused by group link creation and deletion

* Add functionality to search form to search members and group links

* Update groups search box to use the search_url variable

* Incorporate search component into groups search

* Add more tests for the groups group links

* Clean up unnecessary code and comments

* Add ability to sort the Access Granted column

* Fix spelling in fr.yml file

* Add missing french keys
  • Loading branch information
malchua authored Jul 31, 2024
1 parent 2cf91f7 commit 8b2ce02
Show file tree
Hide file tree
Showing 22 changed files with 688 additions and 143 deletions.
7 changes: 7 additions & 0 deletions app/components/groups/table_component.en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
en:
group_name: Group
namespace_name: Source
updated_at: Access Granted
group_access_level: Access Level
expires_at: Expiration
action: Action
7 changes: 7 additions & 0 deletions app/components/groups/table_component.fr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fr:
group_name: Group
namespace_name: Source
updated_at: Access Granted
group_access_level: Access Level
expires_at: Expiration
action: Action
173 changes: 173 additions & 0 deletions app/components/groups/table_component.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<%= render Viral::BaseComponent.new(**wrapper_arguments) do %>
<table
class='
w-full text-sm text-left rtl:text-right text-slate-500 dark:text-slate-400
whitespace-nowrap
'
>
<thead
class='
text-xs text-slate-700 uppercase bg-slate-50 dark:bg-slate-700
dark:text-slate-400
'
>
<tr>
<% @columns.each_with_index do |column, index| %>
<%= render_cell(
tag: 'th',
scope: 'col',
classes: class_names('px-6 py-3', 'sticky left-0 z-10 flex': index.zero?)
) do %>
<%= render Ransack::SortComponent.new(
ransack_obj: @q,
label: t(".#{column}"),
url: helpers.sorting_url(@q, column),
field: column,
) %>
<% end %>
<% end %>
<%= render_cell(
tag: 'th',
scope: 'col',
classes: class_names('px-6 py-3 bg-slate-50 dark:bg-slate-700 sticky right-0')
) do %>
<%= t(".action") %>
<% end %>
</tr>
</thead>
<tbody
class='
bg-white divide-y divide-slate-200 dark:bg-slate-800 dark:divide-slate-700
'
>
<% @namespace_group_links.each do | namespace_group_link | %>
<%= render Viral::BaseComponent.new(**row_arguments(namespace_group_link)) do %>
<% namespace_group_link_source =
helpers.namespace_group_link_source(@namespace, namespace_group_link) %>
<td class="px-6 py-4"><%= namespace_group_link.group.name %></td>
<td class="px-6 py-4">
<% if namespace_group_link_source.key?(:inherited_namespace_path) %>
<%= viral_tooltip(title: t(:"projects.group_links.index.inherited_from")) do %>
<%= link_to namespace_group_link_source[:label],
namespace_group_link_source[:inherited_namespace_path],
data: {
turbo_frame: "_top",
},
class: "text-grey-900 dark:text-grey-100 font-semibold hover:underline" %>
<% end %>
<% else %>
<div class="text-sm font-normal text-slate-500 dark:text-slate-400">
<%= namespace_group_link_source[:label] %>
</div>
<% end %>
</td>
<td class="px-6 py-4">
<div>
<%= viral_time_ago(original_time: namespace_group_link.created_at) %>
<span class="block">
<%= helpers.turbo_frame_tag("invited-group-#{namespace_group_link.group.id}-updated") do %>
<% if namespace_group_link.created_at < namespace_group_link.updated_at %>
<%= render UpdatedComponent.new(updated_at: namespace_group_link.updated_at) %>
<% end %>
<% end %>
</span>
</div>
</td>
<td class="px-6 py-4">
<div class="form-field">
<% if @abilities[:update_namespace] && !namespace_group_link_source.key?(:inherited_namespace_path) %>
<%= form_with(model: namespace_group_link, url: select_group_link_path(namespace_group_link), method: :patch) do |form| %>
<%= form.select(
:group_access_level,
@access_levels,
{ selected: namespace_group_link.group_access_level },
{
id: "invited-group-#{namespace_group_link.group.id}-access-level-select",
onchange: "this.form.requestSubmit();",
"aria-label":
t(:"projects.group_links.index.aria_labels.group_access_level"),
},
) %>
<input type="hidden" name="format" value="turbo_stream"/>
<% end %>
<% else %>
<%= t(
:"projects.group_links.index.access_level.level_#{namespace_group_link.group_access_level}",
) %>
<% end %>
</div>
</td>
<td class="px-6 py-4">
<% if @abilities[:update_namespace] && !namespace_group_link_source.key?(:inherited_namespace_path) %>
<%= form_with( model: namespace_group_link, url: select_group_link_path(namespace_group_link), method: :patch) do |form| %>
<div class="form-field datepicker" data-controller="datepicker">
<div class="relative max-w-sm">
<div
class="
absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none
"
>
<%= viral_icon(
name: :calendar_days,
classes: "w-5 h-5 text-slate-500 dark:text-slate-400",
) %>
</div>
<%= form.text_field :expires_at,
placeholder: I18n.t("date.formats.iso"),
autocomplete: "off",
value: namespace_group_link.expires_at,
id: "invited-group-#{namespace_group_link.group.id}-expiration",
"data-datepicker-target": "datePicker",
"data-datepicker-autosubmit": "true",
"aria-label":
t(:"projects.group_links.index.aria_labels.expires_at") %>

<input type="hidden" name="format" value="turbo_stream"/>
</div>
</div>
<% end %>
<% else %>
<%= namespace_group_link.expires_at %>
<% end %>
</td>
<td class="px-6 py-4 space-x-2">
<% if @abilities[:unlink_group] && !namespace_group_link_source.key?(:inherited_namespace_path) %>
<%= link_to(
t(:"projects.group_links.index.unlink"),
select_group_link_path(namespace_group_link),
data: {
turbo_method: :delete,
turbo_confirm:
t(
:"projects.group_links.index.unlink_confirmation",
namespace_name: namespace_group_link.namespace.human_name,
group_name: namespace_group_link.group.human_name,
),
turbo_stream: true,
},
aria: {
label:
(
t(
:"projects.group_links.index.actions.unlink_aria_label",
member: namespace_group_link.group.name,
)
),
},
class:
"font-medium text-blue-600 underline dark:text-blue-500 hover:no-underline cursor-pointer",
) %>
<% end %>
</td>
<% end %>
<% end %>
</tbody>
</table>
<div class="empty_state_message">
<%= viral_empty(
title: t(:"projects.group_links.index.empty_state.title"),
description: t(:"projects.group_links.index.empty_state.description"),
icon_name: :document_text,
) %>
</div>
<% end %>
61 changes: 61 additions & 0 deletions app/components/groups/table_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# frozen_string_literal: true

module Groups
# Component for rendering a table of Samples
class TableComponent < Component
include Ransack::Helpers::FormHelper

# rubocop:disable Naming/MethodParameterName,Metrics/ParameterLists
def initialize(
namespace_group_links,
namespace,
access_levels,
q,
abilities: {},
**system_arguments
)
@namespace_group_links = namespace_group_links
@namespace = namespace
@access_levels = access_levels
@q = q
@abilities = abilities
@system_arguments = system_arguments

@columns = columns
end
# rubocop:enable Naming/MethodParameterName,Metrics/ParameterLists

def wrapper_arguments
{
tag: 'div',
classes: class_names('table-container relative overflow-x-auto'),
data: { turbo: :temporary }
}
end

def row_arguments(namespace_group_link)
{ tag: 'tr' }.tap do |args|
args[:classes] = class_names('bg-white', 'border-b', 'dark:bg-slate-800', 'dark:border-slate-700')
args[:id] = dom_id(namespace_group_link)
end
end

def render_cell(**arguments, &)
render(Viral::BaseComponent.new(**arguments), &)
end

def select_group_link_path(namespace_group_link)
if @namespace.type == 'Group'
group_group_link_path(@namespace, namespace_group_link)
else
namespace_project_group_link_path(@namespace.parent, @namespace.project, namespace_group_link)
end
end

private

def columns
%i[group_name namespace_name updated_at group_access_level expires_at]
end
end
end
12 changes: 9 additions & 3 deletions app/controllers/concerns/share_actions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ module ShareActions # rubocop:disable Metrics/ModuleLength
end

def index
authorize! @namespace, to: :member_listing?
@q = load_namespace_group_links.ransack(params[:q])
set_default_sort
@pagy, @namespace_group_links = pagy(@q.result)
respond_to do |format|
format.turbo_stream do
@pagy, @namespace_group_links = pagy(load_namespace_group_links)
end
format.turbo_stream
end
end

Expand Down Expand Up @@ -141,4 +143,8 @@ def load_namespace_group_links
authorized_scope(NamespaceGroupLink, type: :relation,
scope_options: { namespace: @namespace })
end

def set_default_sort
@q.sorts = 'group_name asc' if @q.sorts.empty?
end
end
2 changes: 1 addition & 1 deletion app/models/member.rb
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ def ransackable_attributes(_auth_object = nil)
end

def ransackable_associations(_auth_object = nil)
%w[user namespace]
%w[user namespace group]
end
end

Expand Down
8 changes: 8 additions & 0 deletions app/models/namespace_group_link.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ class NamespaceGroupLink < ApplicationRecord
where(namespace:).or(where(namespace: namespace.parent&.self_and_ancestors))
}

def self.ransackable_attributes(_auth_object = nil)
%w[updated_at group_access_level expires_at]
end

def self.ransackable_associations(_auth_object = nil)
%w[group namespace]
end

def send_access_revoked_emails
I18n.available_locales.each do |locale|
user_emails = Member.user_emails(group, locale)
Expand Down
64 changes: 11 additions & 53 deletions app/views/groups/group_links/_invited_groups.html.erb
Original file line number Diff line number Diff line change
@@ -1,53 +1,11 @@
<div class="table-container relative overflow-x-auto" data-turbo-temporary>
<table
class="
w-full
text-sm
text-left
rtl:text-right
text-slate-500
dark:text-slate-400
dark:divide-slate-600
whitespace-nowrap
"
>
<thead
class="
text-xs
text-slate-700
uppercase
bg-slate-50
dark:bg-slate-700
dark:text-slate-400
"
>
<tr>
<th scope="col" class="px-6 py-3"><%= t(:"groups.group_links.index.table_header.group") %></th>
<th scope="col" class="px-6 py-3"><%= t(:"groups.group_links.index.table_header.source") %></th>
<th scope="col" class="px-6 py-3"><%= t(:"groups.group_links.index.table_header.access_granted") %></th>
<th scope="col" class="px-6 py-3 min-w-40"><%= t(:"groups.group_links.index.table_header.access_level") %></th>
<th scope="col" class="px-6 py-3 min-w-48"><%= t(:"groups.group_links.index.table_header.expiration") %></th>
<th scope="col" class="px-6 py-3"><%= t(:"groups.group_links.index.table_header.action") %></th>
</tr>
</thead>
<tbody
id="invited-groups-table-body"
class="
bg-white
divide-y
divide-slate-200
dark:bg-slate-800
dark:divide-slate-700
"
>
<%= render partial: "namespace_group_link", collection: @namespace_group_links %>
</tbody>
</table>
<div class="empty_state_message">
<%= viral_empty(
title: t(:"groups.group_links.index.empty_state.title"),
description: t(:"groups.group_links.index.empty_state.description"),
icon_name: :document_text
) %>
</div>
</div>
<%= render Groups::TableComponent.new(
@namespace_group_links,
@namespace,
@access_levels,
@q,
abilities: {
update_namespace:
allowed_to?(:update_namespace_with_group_link?, @namespace),
unlink_group: allowed_to?(:unlink_namespace_with_group?, @namespace),
},
) %>
9 changes: 6 additions & 3 deletions app/views/groups/group_links/create.turbo_stream.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
<% if @group_invited && @tab == 'invited_groups' %>
<%= turbo_stream.update "members", partial: "invited_groups" %>

<%= turbo_stream.update "members_pagination", partial: "pagination" %>
<%= turbo_stream.replace "members" do %>
<%= turbo_frame_tag "members",
src: group_group_links_path(@group, format: :turbo_stream) do %>
<%= render partial: "shared/loading/table" %>
<% end %>
<% end %>
<% end %>

<% if @created_namespace_group_link %>
Expand Down
9 changes: 6 additions & 3 deletions app/views/groups/group_links/destroy.turbo_stream.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
<% end %>

<% if @namespace_group_link&.deleted? %>
<%= turbo_stream.update "members", partial: "invited_groups" %>

<%= turbo_stream.update "members_pagination", partial: "pagination" %>
<%= turbo_stream.replace "members" do %>
<%= turbo_frame_tag "members",
src: group_group_links_path(@group, format: :turbo_stream) do %>
<%= render partial: "shared/loading/table" %>
<% end %>
<% end %>
<% end %>
Loading

0 comments on commit 8b2ce02

Please sign in to comment.