diff --git a/app/assets/stylesheets/shared/content_pane.scss b/app/assets/stylesheets/shared/content_pane.scss index 912685141..7ce218414 100644 --- a/app/assets/stylesheets/shared/content_pane.scss +++ b/app/assets/stylesheets/shared/content_pane.scss @@ -21,6 +21,10 @@ &.user-groups-table-container { height: calc(100vh - var(--content-header-size) - var(--navbar-height) - 72px); } + + &.user-group-table-container { + height: calc(100vh - var(--content-header-size) - var(--navbar-height) - 104px); + } } .content-header { diff --git a/app/controllers/users/settings/user_group_memberships_controller.rb b/app/controllers/users/settings/user_group_memberships_controller.rb index c05070bbf..c36021087 100644 --- a/app/controllers/users/settings/user_group_memberships_controller.rb +++ b/app/controllers/users/settings/user_group_memberships_controller.rb @@ -3,11 +3,25 @@ module Users module Settings class UserGroupMembershipsController < ApplicationController - before_action :load_team, except: :index + before_action :load_team before_action :load_user_group before_action :check_manage_permissions, except: %i(index show) - def index; end + def index + memberships = Lists::UserGroupMembershipsService.new(@user_group.user_group_memberships, params).call + render json: memberships, each_serializer: Lists::UserGroupMembershipSerializer, user: current_user, meta: pagination_dict(memberships) + end + + def actions_toolbar + render json: { + actions: + Toolbars::UserGroupMembershipsService.new( + current_user, + @user_group, + user_group_membership_ids: JSON.parse(params[:items]).pluck('id') + ).actions + } + end def show; end @@ -27,7 +41,15 @@ module Users end end - def destroy; end + def destroy_multiple + members = @user_group.user_group_memberships.where(id: params[:membership_ids]) + + if members.destroy_all + render json: { message: :success }, status: :ok + else + head :unprocessable_entity + end + end private diff --git a/app/controllers/users/settings/user_groups_controller.rb b/app/controllers/users/settings/user_groups_controller.rb index 85f271749..83731ceed 100644 --- a/app/controllers/users/settings/user_groups_controller.rb +++ b/app/controllers/users/settings/user_groups_controller.rb @@ -39,7 +39,9 @@ module Users end end - def show; end + def show + @active_tab = :user_groups + end def create @user_group = @team.user_groups.new diff --git a/app/javascript/packs/vue/user_groups_show.js b/app/javascript/packs/vue/user_groups_show.js new file mode 100644 index 000000000..f4929ca5c --- /dev/null +++ b/app/javascript/packs/vue/user_groups_show.js @@ -0,0 +1,10 @@ +import { createApp } from 'vue/dist/vue.esm-bundler.js'; +import PerfectScrollbar from 'vue3-perfect-scrollbar'; +import UserGroupShow from '../../vue/user_groups/show.vue'; +import { mountWithTurbolinks } from './helpers/turbolinks.js'; + +const app = createApp(); +app.component('UserGroupShow', UserGroupShow); +app.config.globalProperties.i18n = window.I18n; +app.use(PerfectScrollbar); +mountWithTurbolinks(app, '#userGroupShow'); diff --git a/app/javascript/vue/user_groups/modal/add_member.vue b/app/javascript/vue/user_groups/modal/add_member.vue new file mode 100644 index 000000000..2b70dc230 --- /dev/null +++ b/app/javascript/vue/user_groups/modal/add_member.vue @@ -0,0 +1,97 @@ + + + diff --git a/app/javascript/vue/user_groups/modal/create_group.vue b/app/javascript/vue/user_groups/modal/create_group.vue index a8c3f548d..0b9aaa0e8 100644 --- a/app/javascript/vue/user_groups/modal/create_group.vue +++ b/app/javascript/vue/user_groups/modal/create_group.vue @@ -78,25 +78,11 @@ export default { computed: { validName() { return this.name.length >= GLOBAL_CONSTANTS.NAME_MIN_LENGTH; - }, - modalHeader() { - if (this.createUrl) { - return this.i18n.t('projects.index.modal_new_project.modal_title'); - } - - return this.i18n.t('projects.index.modal_edit_project.modal_title', { project: this.project?.name }); - }, - submitButtonLabel() { - if (this.createUrl) { - return this.i18n.t('projects.index.modal_new_project.create'); - } - - return this.i18n.t('projects.index.modal_edit_project.submit'); } }, data() { return { - name: this.project?.name || '', + name: '', users: [], submitting: false }; diff --git a/app/javascript/vue/user_groups/show.vue b/app/javascript/vue/user_groups/show.vue new file mode 100644 index 000000000..ffb7c5fb8 --- /dev/null +++ b/app/javascript/vue/user_groups/show.vue @@ -0,0 +1,120 @@ + + + diff --git a/app/models/user_group_membership.rb b/app/models/user_group_membership.rb index 293db6444..fb0629b27 100644 --- a/app/models/user_group_membership.rb +++ b/app/models/user_group_membership.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class UserGroupMembership < ApplicationRecord + include SearchableModel + belongs_to :user_group belongs_to :user belongs_to :created_by, class_name: 'User' diff --git a/app/serializers/lists/user_group_membership_serializer.rb b/app/serializers/lists/user_group_membership_serializer.rb new file mode 100644 index 000000000..1726959e5 --- /dev/null +++ b/app/serializers/lists/user_group_membership_serializer.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module Lists + class UserGroupMembershipSerializer < ActiveModel::Serializer + include Rails.application.routes.url_helpers + include Canaid::Helpers::PermissionsHelper + + attributes :id, :created_at, :name, :email + + def name + object.user.full_name + end + + def email + object.user.email + end + + def created_at + I18n.l(object.created_at, format: :full_date) + end + end +end diff --git a/app/services/lists/user_group_memberships_service.rb b/app/services/lists/user_group_memberships_service.rb new file mode 100644 index 000000000..49dba47c8 --- /dev/null +++ b/app/services/lists/user_group_memberships_service.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Lists + class UserGroupMembershipsService < BaseService + private + + def fetch_records + @records = @raw_data.joins(:user).includes(:user) + end + + def filter_records + return unless @params[:search] + + @records = @records.where_attributes_like( + ['users.full_name', 'users.email'], + @params[:search] + ) + end + + def sortable_columns + @sortable_columns ||= { + name: 'users.full_name', + email: 'users.email', + created_at: 'user_group_memberships.created_at' + } + end + end +end diff --git a/app/services/toolbars/user_group_memberships_service.rb b/app/services/toolbars/user_group_memberships_service.rb new file mode 100644 index 000000000..191c2ad61 --- /dev/null +++ b/app/services/toolbars/user_group_memberships_service.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Toolbars + class UserGroupMembershipsService + attr_reader :current_user + + include Canaid::Helpers::PermissionsHelper + include Rails.application.routes.url_helpers + + def initialize(current_user, user_group, user_group_membership_ids: []) + @current_user = current_user + @user_group = user_group + @team = user_group.team + @memberships = @user_group.user_group_memberships.where(id: user_group_membership_ids) + + @single = @memberships.length == 1 + end + + def actions + return [] if @memberships.none? + + [ + delete_action + ].compact + end + + private + + def delete_action + return unless can_manage_team?(@team) + + { + name: 'delete', + label: 'Remove', + icon: 'sn-icon sn-icon-delete', + path: destroy_multiple_users_settings_team_user_group_user_group_memberships_path(@team, @user_group, membership_ids: @memberships.pluck(:id)), + type: :emit + } + end + end +end diff --git a/app/views/users/settings/user_groups/show.html.erb b/app/views/users/settings/user_groups/show.html.erb new file mode 100644 index 000000000..ed7ce355b --- /dev/null +++ b/app/views/users/settings/user_groups/show.html.erb @@ -0,0 +1,28 @@ +<% provide(:head_title, t("users.settings.teams.head_title")) %> +<% provide(:container_class, "no-second-nav-container") %> + +<% content_for :head do %> + +<% end %> + +
+ <%= render partial: 'users/settings/teams/header' %> +
+ <%= link_to users_settings_team_user_groups_path, class: "hover:text-black text-black hover:no-underline" do %> + + <% end %> +

+ <%= t('user_groups.show.title', group: @user_group.name) %> +

+
+
+ +
+
+<%= javascript_include_tag 'vue_user_groups_show' %> diff --git a/config/initializers/extends.rb b/config/initializers/extends.rb index 03654ee2b..ead0f05ab 100644 --- a/config/initializers/extends.rb +++ b/config/initializers/extends.rb @@ -757,6 +757,7 @@ class Extends teams/show teams/members user_groups/index + user_groups/show ) DEFAULT_USER_NOTIFICATION_SETTINGS = { diff --git a/config/locales/en.yml b/config/locales/en.yml index c45ee4d10..d383a2744 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4217,6 +4217,25 @@ en: confirm: "Delete group" toolbar: delete: "Delete" + show: + title: "Group %{group}" + add_members: "Add members" + name: "Name" + email: "Email" + created_at: "Added on" + remove: "Remove" + add_members_modal: + title: "Add members" + select_members: "Select members" + select_members_placeholder: "Search for members" + success: "Members added successfully." + error: "There was a problem adding members. Please try again." + remove_modal: + title: "Remove member from %{group}" + description_html: "You are about to remove %{number} member(s) from this group.

Group-based access to content will be removed.

Are you sure you want to remove members from group?" + confirm: "Remove" + success: "Members removed successfully." + error: "There was a problem removing members. Please try again." invite_users: to_team: title: "Invite members to %{team}" diff --git a/config/routes.rb b/config/routes.rb index 40fb76cc6..94f60b2fe 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -150,7 +150,12 @@ Rails.application.routes.draw do resources :teams, only: [] do resources :user_groups, only: %i(index create update destroy show) do - resources :user_group_memberships, only: %i(index create update destroy) + resources :user_group_memberships, only: %i(index create update) do + collection do + delete :destroy_multiple + post :actions_toolbar + end + end collection do get :unassigned_users post :actions_toolbar diff --git a/config/webpack/webpack.config.js b/config/webpack/webpack.config.js index 51cafc7e3..2ef9dc729 100644 --- a/config/webpack/webpack.config.js +++ b/config/webpack/webpack.config.js @@ -75,7 +75,8 @@ const entryList = { vue_design_system_table: './app/javascript/packs/vue/design_system/table.js', vue_favorites_widget: './app/javascript/packs/vue/favorites_widget.js', vue_experiment_description_modal: './app/javascript/packs/vue/experiment_description_modal.js', - vue_user_groups_table: './app/javascript/packs/vue/user_groups_table.js' + vue_user_groups_table: './app/javascript/packs/vue/user_groups_table.js', + vue_user_groups_show: './app/javascript/packs/vue/user_groups_show.js' }; // Engine pack loading based on https://github.com/rails/webpacker/issues/348#issuecomment-635480949