+<% session[:team_id] = @team.id %>
+
+<%= form_tag :action => 'create' do %>
+ <%= hidden_field_tag 'id', @team.id %>
+ Enter user login: <%= text_field_with_auto_complete :user, :name, {:size => 41} %>
+
+<% end %>
+
+
+
+<%= link_to 'Back', :controller => 'course_team', :action => 'list', :id => @team.parent_id %>
From 80963e70ef3a295c816c292e1746fca7aefcedbb Mon Sep 17 00:00:00 2001
From: manideepika21
Date: Mon, 25 Nov 2024 10:57:23 -0500
Subject: [PATCH 04/18] Modified teams_users model name to teams_participants
---
...s_participants_controller => teams_participants_controller.rb} | 0
app/models/{teams_user.rb => teams_participants.rb} | 0
2 files changed, 0 insertions(+), 0 deletions(-)
rename app/controllers/{teams_participants_controller => teams_participants_controller.rb} (100%)
rename app/models/{teams_user.rb => teams_participants.rb} (100%)
diff --git a/app/controllers/teams_participants_controller b/app/controllers/teams_participants_controller.rb
similarity index 100%
rename from app/controllers/teams_participants_controller
rename to app/controllers/teams_participants_controller.rb
diff --git a/app/models/teams_user.rb b/app/models/teams_participants.rb
similarity index 100%
rename from app/models/teams_user.rb
rename to app/models/teams_participants.rb
From 4dbe2443af22bc1a5ad82ce7e6a11e3aaafa68f9 Mon Sep 17 00:00:00 2001
From: bhuvan chandra kurra
Date: Fri, 29 Nov 2024 13:10:31 -0500
Subject: [PATCH 05/18] Updated teams_participants_controller - Refactored the
logic for adding/removing users to Team and TeamUser models - Cleaned up
create action - Refgactored delete method - Improved list method - Enhance
delete_selected method
---
.../teams_participants_controller.rb | 150 ++++++++----------
1 file changed, 66 insertions(+), 84 deletions(-)
diff --git a/app/controllers/teams_participants_controller.rb b/app/controllers/teams_participants_controller.rb
index b0b716106..5e265df51 100644
--- a/app/controllers/teams_participants_controller.rb
+++ b/app/controllers/teams_participants_controller.rb
@@ -1,112 +1,94 @@
class TeamsUsersController < ApplicationController
include AuthorizationHelper
+ # Check permissions for actions
def action_allowed?
- # Allow duty updation for a team if current user is student, else require TA or above Privileges.
- if %w[update_duties].include? params[:action]
+ if %w[update_duties].include?(params[:action])
current_user_has_student_privileges?
else
current_user_has_ta_privileges?
end
end
+ # Autocomplete user names for adding members to a team
def auto_complete_for_user_name
- team = Team.find(session[:team_id])
- @users = team.get_possible_team_members(params[:user][:name])
- render inline: "<%= auto_complete_result @users, 'name' %>", layout: false
+ team = Team.find_by(id: session[:team_id])
+ if team
+ @users = team.get_members.where("name LIKE ?", "%#{params[:user][:name]}%")
+ render inline: "<%= auto_complete_result @users, 'name' %>", layout: false
+ else
+ render plain: "Team not found", status: :not_found
+ end
end
- # Example of duties: manager, designer, programmer, tester. Finds TeamsUser and save preferred Duty
- def update_duties
- team_user = TeamsUser.find(params[:teams_user_id])
- team_user.update_attribute(:duty_id, params[:teams_user]['duty_id'])
- redirect_to controller: 'student_teams', action: 'view', student_id: params[:participant_id]
- end
+ # Add a user to a team
+ def create
+ user = User.find_by(name: params[:user][:name].strip)
+ return redirect_with_error("User not found. Please create the user.") unless user
- def list
- @team = Team.find(params[:id])
- @assignment = Assignment.find(@team.parent_id)
- @teams_users = TeamsUser.page(params[:page]).per_page(10).where(['team_id = ?', params[:id]])
- end
+ team = Team.find(params[:team_id])
+ begin
+ team.add_member(user)
+ flash[:success] = "User #{user.name} successfully added to the team."
+ rescue StandardError => e
+ flash[:error] = e.message
+ end
- def new
- @team = Team.find(params[:id])
+ redirect_to action: 'list', id: team.assignment_id
end
- def create
- user = User.find_by(name: params[:user][:name].strip)
- unless user
- urlCreate = url_for controller: 'users', action: 'new'
- flash[:error] = "\"#{params[:user][:name].strip}\" is not defined. Please create this user before continuing."
- end
+ # Remove a user from a team
+ def delete
+ team = Team.find(params[:team_id])
+ user = User.find(params[:user_id])
- team = Team.find(params[:id])
- unless user.nil?
- if team.is_a?(AssignmentTeam)
- assignment = Assignment.find(team.parent_id)
- if assignment.user_on_team?(user)
- flash[:error] = "This user is already assigned to a team for this assignment"
- redirect_back fallback_location: root_path
- return
- end
- if AssignmentParticipant.find_by(user_id: user.id, parent_id: assignment.id).nil?
- urlAssignmentParticipantList = url_for controller: 'participants', action: 'list', id: assignment.id, model: 'Assignment', authorization: 'participant'
- flash[:error] = "\"#{user.name}\" is not a participant of the current assignment. Please add this user before continuing."
- else
- begin
- add_member_return = team.add_member(user, team.parent_id)
- rescue
- flash[:error] = "The user #{user.name} is already a member of the team #{team.name}"
- redirect_back fallback_location: root_path
- return
- end
- flash[:error] = 'This team already has the maximum number of members.' if add_member_return == false
- end
- else # CourseTeam
- course = Course.find(team.parent_id)
- if course.user_on_team?(user)
- flash[:error] = "This user is already assigned to a team for this course"
- redirect_back fallback_location: root_path
- return
- end
- if CourseParticipant.find_by(user_id: user.id, parent_id: course.id).nil?
- urlCourseParticipantList = url_for controller: 'participants', action: 'list', id: course.id, model: 'Course', authorization: 'participant'
- flash[:error] = "\"#{user.name}\" is not a participant of the current course. Please add this user before continuing."
- else
- begin
- add_member_return = team.add_member(user, team.parent_id)
- rescue
- flash[:error] = "The user #{user.name} is already a member of the team #{team.name}"
- redirect_back fallback_location: root_path
- return
- end
- flash[:error] = 'This team already has the maximum number of members.' if add_member_return == false
- if add_member_return
- @teams_user = TeamsUser.last
- undo_link("The team user \"#{user.name}\" has been successfully added to \"#{team.name}\".")
- end
- end
- end
+ begin
+ team.remove_member(user)
+ flash[:success] = "User #{user.name} successfully removed from the team."
+ rescue StandardError => e
+ flash[:error] = e.message
end
- redirect_to controller: 'teams', action: 'list', id: team.parent_id
+ redirect_to action: 'list', id: team.assignment_id
end
- def delete
- @teams_user = TeamsUser.find(params[:id])
- parent_id = Team.find(@teams_user.team_id).parent_id
- @user = User.find(@teams_user.user_id)
- @teams_user.destroy
- undo_link("The team user \"#{@user.name}\" has been successfully removed. ")
- redirect_to controller: 'teams', action: 'list', id: parent_id
+ # List all members of a team
+ def list
+ @team = Team.find(params[:id])
+ @assignment = @team.assignment
+ @teams_users = @team.teams_users.page(params[:page]).per_page(10)
end
+ # Update a team user's duties
+ def update_duties
+ team_user = TeamsUser.find(params[:teams_user_id])
+ if team_user
+ team_user.update!(duty_id: params[:teams_user][:duty_id])
+ redirect_to controller: 'student_teams', action: 'view', student_id: params[:participant_id]
+ else
+ flash[:error] = "Team member not found."
+ redirect_back fallback_location: root_path
+ end
+ rescue StandardError => e
+ flash[:error] = e.message
+ redirect_back fallback_location: root_path
+ end
+
+ # Delete selected users from a team
def delete_selected
- params[:item].each do |item_id|
- team_user = TeamsUser.find(item_id).first
- team_user.destroy
+ team = Team.find(params[:id])
+ user_ids = params[:item]
+
+ begin
+ user_ids.each do |user_id|
+ user = User.find(user_id)
+ team.remove_member(user)
+ end
+ flash[:success] = "Selected users successfully removed from the team."
+ rescue StandardError => e
+ flash[:error] = e.message
end
- redirect_to action: 'list', id: params[:id]
+ redirect_to action: 'list', id: team.assignment_id
end
-end
+end
\ No newline at end of file
From 88af789948a051d0ad9ddb4443fc4636c1d37af0 Mon Sep 17 00:00:00 2001
From: bhuvan chandra kurra
Date: Fri, 29 Nov 2024 13:13:09 -0500
Subject: [PATCH 06/18] Moved logic of adding and removing team members to
teams model
---
app/models/team.rb | 82 ++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 76 insertions(+), 6 deletions(-)
diff --git a/app/models/team.rb b/app/models/team.rb
index afb8ac66f..073eac951 100644
--- a/app/models/team.rb
+++ b/app/models/team.rb
@@ -6,15 +6,85 @@ class Team < ApplicationRecord
belongs_to :assignment
attr_accessor :max_participants
- # TODO Team implementing Teams controller and model should implement this method better.
- # TODO partial implementation here just for the functionality needed for join_team_tequests controller
+ # Check if the team is full
def full?
+ max_participants ||= 3 # Default maximum participants
+ participants.count >= max_participants
+ end
+
+ # Add a user to the team
+ def add_member(user)
+ return false if full?
+
+ TeamsUser.create!(team_id: id, user_id: user.id)
+ rescue ActiveRecord::RecordInvalid => e
+ raise "Failed to add member to the team: #{e.message}"
+ end
+
+ # Remove a user from the team
+ def remove_member(user)
+ team_user = teams_users.find_by(user_id: user.id)
+ if team_user
+ team_user.destroy
+ else
+ raise "The user #{user.name} is not a member of the team."
+ end
+ rescue StandardError => e
+ raise "Failed to remove member from the team: #{e.message}"
+ end
+
+ # Check if a user belongs to this team
+ def has_member?(user)
+ users.exists?(id: user.id)
+ end
+
+ # Get all team members
+ def get_members
+ users
+ end
- max_participants ||= 3
- if participants.count >= max_participants
- true
+ # Assign a leader to the team
+ def assign_leader(user)
+ team_user = teams_users.find_by(user_id: user.id)
+ if team_user
+ team_user.update!(role: 'leader')
else
- false
+ raise "The user #{user.name} is not a member of the team."
+ end
+ rescue StandardError => e
+ raise "Failed to assign team leader: #{e.message}"
+ end
+
+ # Get the team leader
+ def team_leader
+ teams_users.find_by(role: 'leader')&.user
+ end
+
+ # Check if the team is empty
+ def empty?
+ users.empty?
+ end
+
+ # Transfer all members to another team
+ def transfer_members_to(other_team)
+ raise "Cannot transfer members to a full team." if other_team.full?
+
+ users.each do |user|
+ other_team.add_member(user)
+ remove_member(user)
+ end
+ rescue StandardError => e
+ raise "Failed to transfer members: #{e.message}"
+ end
+
+ # Auto-assign users to the team
+ def auto_assign_users(users_to_add)
+ users_to_add.each do |user|
+ break if full?
+
+ add_member(user)
end
+ rescue StandardError => e
+ raise "Failed to auto-assign users: #{e.message}"
end
end
\ No newline at end of file
From bcd48029ea76cb4b28d394a9f4e3cbcd459f3d76 Mon Sep 17 00:00:00 2001
From: bhuvan chandra kurra
Date: Fri, 29 Nov 2024 13:15:31 -0500
Subject: [PATCH 07/18] Moved logic of adding/removing user to/from a team to
teams_participants model
---
app/models/team.rb | 17 ---------
app/models/teams_participants.rb | 60 ++++++++++++++++++++++++++------
2 files changed, 50 insertions(+), 27 deletions(-)
diff --git a/app/models/team.rb b/app/models/team.rb
index 073eac951..7291daf60 100644
--- a/app/models/team.rb
+++ b/app/models/team.rb
@@ -43,23 +43,6 @@ def get_members
users
end
- # Assign a leader to the team
- def assign_leader(user)
- team_user = teams_users.find_by(user_id: user.id)
- if team_user
- team_user.update!(role: 'leader')
- else
- raise "The user #{user.name} is not a member of the team."
- end
- rescue StandardError => e
- raise "Failed to assign team leader: #{e.message}"
- end
-
- # Get the team leader
- def team_leader
- teams_users.find_by(role: 'leader')&.user
- end
-
# Check if the team is empty
def empty?
users.empty?
diff --git a/app/models/teams_participants.rb b/app/models/teams_participants.rb
index 9e1768b94..a1e4b53e3 100644
--- a/app/models/teams_participants.rb
+++ b/app/models/teams_participants.rb
@@ -2,22 +2,62 @@ class TeamsUser < ApplicationRecord
belongs_to :user
belongs_to :team
+ # Returns the user's name. If an IP address is provided, it may influence the name retrieval logic.
def name(ip_address = nil)
- name = user.name(ip_address)
+ user.name(ip_address)
end
- def get_team_members(team_id)
- team_members = TeamsUser.where('team_id = ?', team_id)
- user_ids = team_members.pluck(:user_id)
- users = User.where(id: user_ids)
+ # Retrieves all team members for a given team ID as a collection of User objects.
+ # Allows optional exclusion of certain roles.
+ def self.get_team_members(team_id, excluded_roles: [])
+ users = where(team_id: team_id).includes(:user).map(&:user)
+ return users if excluded_roles.empty?
- return users
+ # Exclude users with specific roles, if any
+ users.reject { |user| excluded_roles.include?(user.role) }
end
- # Removes entry in the TeamUsers table for the given user and given team id
- def self.remove_team(user_id, team_id)
- team_user = TeamsUser.where('user_id = ? and team_id = ?', user_id, team_id).first
- team_user&.destroy
+ # Adds a user to a team. Raises an error if the user is already on the team.
+ # Returns the created TeamsUser object if successful.
+ def self.add_to_team(user_id, team_id)
+ # Check if the user is already a team member
+ if where(user_id: user_id, team_id: team_id).exists?
+ raise "The user is already a member of the team."
+ end
+
+ # Create the association
+ create!(user_id: user_id, team_id: team_id)
+ rescue ActiveRecord::RecordInvalid => e
+ raise "Failed to add user to team: #{e.message}"
end
+ # Removes a user's association with a team. Raises an error if the association does not exist.
+ def self.remove_from_team(user_id, team_id)
+ team_user = find_by(user_id: user_id, team_id: team_id)
+ raise "The user is not a member of this team." if team_user.nil?
+
+ team_user.destroy
+ rescue StandardError => e
+ raise "Failed to remove user from team: #{e.message}"
+ end
+
+ # Transfers a user from one team to another within the same context.
+ # Ensures that the user is removed from the previous team before adding to the new one.
+ def self.transfer_user_to_team(user_id, old_team_id, new_team_id)
+ remove_from_team(user_id, old_team_id)
+ add_to_team(user_id, new_team_id)
+ rescue StandardError => e
+ raise "Failed to transfer user between teams: #{e.message}"
+ end
+
+ # Checks if a user is already on a team.
+ def self.user_on_team?(user_id, team_id)
+ where(user_id: user_id, team_id: team_id).exists?
+ end
+
+ # Retrieves all teams for a given user as a collection of Team objects.
+ def self.get_teams_for_user(user_id)
+ team_ids = where(user_id: user_id).pluck(:team_id)
+ Team.where(id: team_ids)
+ end
end
From 22cdee93339307ce9a2536299e7100976d1f5f05 Mon Sep 17 00:00:00 2001
From: manideepika21
Date: Sat, 30 Nov 2024 12:27:52 -0500
Subject: [PATCH 08/18] Moved teams model from expertiza to
reimplementation-back-end repository
---
.../teams_participants_controller.rb | 4 +-
app/models/assignment.rb | 27 ++
app/models/course.rb | 123 +++++--
app/models/course_participant.rb | 35 ++
app/models/team.rb | 324 +++++++++++++++++-
app/models/teams_participants.rb | 60 +++-
6 files changed, 514 insertions(+), 59 deletions(-)
create mode 100644 app/models/course_participant.rb
diff --git a/app/controllers/teams_participants_controller.rb b/app/controllers/teams_participants_controller.rb
index b0b716106..e445a0345 100644
--- a/app/controllers/teams_participants_controller.rb
+++ b/app/controllers/teams_participants_controller.rb
@@ -1,4 +1,4 @@
-class TeamsUsersController < ApplicationController
+class TeamsParticipantsController < ApplicationController
include AuthorizationHelper
def action_allowed?
@@ -109,4 +109,4 @@ def delete_selected
redirect_to action: 'list', id: params[:id]
end
-end
+end
\ No newline at end of file
diff --git a/app/models/assignment.rb b/app/models/assignment.rb
index 45e8d2acf..e4b6ed13b 100644
--- a/app/models/assignment.rb
+++ b/app/models/assignment.rb
@@ -26,6 +26,33 @@ def num_review_rounds
rounds_of_reviews
end
+ # Check if a user is on a team in this assignment
+ def user_on_team?(user)
+ teams.joins(:users).exists?(users: { id: user.id })
+ end
+
+ # Add a user to a team for this assignment
+ def add_user_to_team(user, team_id)
+ team = teams.find_by(id: team_id)
+ raise "Team not found in this assignment." unless team
+
+ if user_on_team?(user)
+ raise "The user #{user.name} is already assigned to a team for this assignment."
+ end
+
+ team.add_member(user, id)
+ rescue StandardError => e
+ raise "Failed to add user to team: #{e.message}"
+ end
+
+ # Validate if a user is a participant of this assignment
+ def validate_participant(user)
+ participant = participants.find_by(user_id: user.id)
+ raise "User #{user.name} is not a participant of this assignment." unless participant
+
+ participant
+ end
+
# Add a participant to the assignment based on the provided user_id.
# This method first finds the User with the given user_id. If the user does not exist, it raises an error.
# It then checks if the user is already a participant in the assignment. If so, it raises an error.
diff --git a/app/models/course.rb b/app/models/course.rb
index 9e70ccf7d..c7ed5b506 100644
--- a/app/models/course.rb
+++ b/app/models/course.rb
@@ -1,52 +1,101 @@
class Course < ApplicationRecord
+ enum locale: Locale.code_name_to_db_encoding
+
+ # Associations
+ has_many :ta_mappings, dependent: :destroy
+ has_many :tas, through: :ta_mappings
+ has_many :assignments, dependent: :destroy
belongs_to :instructor, class_name: 'User', foreign_key: 'instructor_id'
- belongs_to :institution, foreign_key: 'institution_id'
+ belongs_to :institution, foreign_key: 'institutions_id'
+ has_many :participants, class_name: 'CourseParticipant', foreign_key: 'parent_id', dependent: :destroy
+ has_many :course_teams, foreign_key: 'parent_id', dependent: :destroy
+ has_one :course_node, foreign_key: 'node_object_id', dependent: :destroy
+ has_many :notifications, dependent: :destroy
+ has_paper_trail
+
+ # Validations
validates :name, presence: true
validates :directory_path, presence: true
- has_many :ta_mappings, dependent: :destroy
- has_many :tas, through: :ta_mappings
- # Returns the submission directory for the course
- def path
- raise 'Path can not be created as the course must be associated with an instructor.' if instructor_id.nil?
- Rails.root + '/' + Institution.find(institution_id).name.gsub(" ", "") + '/' + User.find(instructor_id).name.gsub(" ", "") + '/' + directory_path + '/'
+ # Return teams associated with this course
+ def get_teams
+ course_teams
end
- # Add a Teaching Assistant to the course
- def add_ta(user)
- if user.nil?
- return { success: false, message: "The user with id #{user.id} does not exist" }
- elsif TaMapping.exists?(ta_id: user.id, course_id: id)
- return { success: false, message: "The user with id #{user.id} is already a TA for this course." }
+ # Get all participants in this course
+ def get_participants
+ participants
+ end
+
+ # Get a specific participant by user ID
+ def get_participant(user_id)
+ participants.find_by(user_id: user_id)
+ end
+
+ # Check if a user is on any team in the course
+ def user_on_team?(user)
+ course_teams.joins(:users).exists?(users: { id: user.id })
+ end
+
+ # Add a user as a participant to this course
+ def add_participant(user_name)
+ user = User.find_by(name: user_name)
+ raise "No user account exists with the name #{user_name}. Please create the user first." unless user
+
+ participant = participants.find_by(user_id: user.id)
+ if participant
+ raise "The user #{user.name} is already a participant."
else
- ta_mapping = TaMapping.create(ta_id: user.id, course_id: id)
- user.update(role: Role::TEACHING_ASSISTANT)
- if ta_mapping.save
- return { success: true, data: ta_mapping.slice(:course_id, :ta_id) }
- else
- return { success: false, message: ta_mapping.errors }
- end
+ participants.create(user_id: user.id, permission_granted: user.master_permission_granted)
end
end
- # Removes Teaching Assistant from the course
- def remove_ta(ta_id)
- ta_mapping = ta_mappings.find_by(ta_id: ta_id, course_id: :id)
- return { success: false, message: "No TA mapping found for the specified course and TA" } if ta_mapping.nil?
- ta = User.find(ta_mapping.ta_id)
- ta_count = TaMapping.where(ta_id: ta_id).size - 1
- if ta_count.zero?
- ta.update(role: Role::STUDENT)
+ def remove_participants(user_ids)
+ user_ids.each do |user_id|
+ participant = participants.find_by(user_id: user_id)
+ raise "User with ID #{user_id} is not a participant." if participant.nil?
+
+ participant.destroy
end
- ta_mapping.destroy
- { success: true, ta_name: ta.name }
end
- # Creates a copy of the course
- def copy_course
- new_course = dup
- new_course.directory_path += '_copy'
- new_course.name += '_copy'
- new_course.save
+ # Add a user to a team
+ def add_user_to_team(user, team_id)
+ team = course_teams.find_by(id: team_id)
+ raise "Team not found in this course." unless team
+
+ if user_on_team?(user)
+ raise "The user #{user.name} is already assigned to a team for this course."
+ end
+
+ team.add_member(user, id)
end
-end
\ No newline at end of file
+
+ # Copy participants from an assignment to this course
+ def copy_participants_from_assignment(assignment_id)
+ participants = AssignmentParticipant.where(parent_id: assignment_id)
+ errors = []
+
+ participants.each do |participant|
+ user = User.find(participant.user_id)
+ begin
+ add_participant(user.name)
+ rescue StandardError => e
+ errors << e.message
+ end
+ end
+
+ raise errors.join(' ') unless errors.empty?
+ end
+
+ # Returns the path for this course
+ def path
+ raise 'Path cannot be created. The course must be associated with an instructor.' if instructor_id.nil?
+
+ Rails.root.join('pg_data', FileHelper.clean_path(instructor.name), FileHelper.clean_path(directory_path))
+ end
+
+ # Analytics
+ require 'analytic/course_analytic'
+ include CourseAnalytic
+end
diff --git a/app/models/course_participant.rb b/app/models/course_participant.rb
new file mode 100644
index 000000000..6c284130a
--- /dev/null
+++ b/app/models/course_participant.rb
@@ -0,0 +1,35 @@
+class CourseParticipant < Participant
+ belongs_to :course, class_name: 'Course', foreign_key: 'parent_id'
+
+ # Copy this participant to an assignment
+ def copy_to_assignment(assignment_id)
+ part = AssignmentParticipant.find_or_create_by(user_id: user_id, parent_id: assignment_id)
+ part.set_handle if part.persisted?
+ part
+ end
+
+ # Provide import functionality for Course Participants
+ def self.import(row_hash, session, course_id)
+ raise ArgumentError, 'No user ID has been specified.' if row_hash.empty?
+
+ user = User.find_by(name: row_hash[:username])
+ unless user
+ raise ArgumentError, "The record containing #{row_hash[:username]} does not have enough items." if row_hash.length < 4
+
+ attributes = ImportFileHelper.define_attributes(row_hash)
+ user = ImportFileHelper.create_new_user(attributes, session)
+ end
+
+ course = Course.find(course_id)
+ raise ImportError, "The course with the ID #{course_id} was not found." unless course
+
+ unless exists?(user_id: user.id, parent_id: course_id)
+ create(user_id: user.id, parent_id: course_id)
+ end
+ end
+
+ # Generate a path for this participant
+ def path
+ course.path.join(directory_num.to_s)
+ end
+end
diff --git a/app/models/team.rb b/app/models/team.rb
index afb8ac66f..94cc9a90a 100644
--- a/app/models/team.rb
+++ b/app/models/team.rb
@@ -1,20 +1,324 @@
class Team < ApplicationRecord
- has_many :signed_up_teams, dependent: :destroy
has_many :teams_users, dependent: :destroy
has_many :users, through: :teams_users
- has_many :participants
- belongs_to :assignment
- attr_accessor :max_participants
+ has_many :join_team_requests, dependent: :destroy
+ has_one :team_node, foreign_key: :node_object_id, dependent: :destroy
+ has_many :signed_up_teams, dependent: :destroy
+ has_many :bids, dependent: :destroy
+ has_paper_trail
+
+ scope :find_team_for_assignment_and_user, lambda { |assignment_id, user_id|
+ joins(:teams_users).where('teams.parent_id = ? AND teams_users.user_id = ?', assignment_id, user_id)
+ }
+
+ # Allowed types of teams -- ASSIGNMENT teams or COURSE teams
+ def self.allowed_types
+ # non-interpolated array of single-quoted strings
+ %w[Assignment Course]
+ end
+
+ # Get the participants of the given team
+ def participants
+ users.where(parent_id: parent_id || current_user_id).flat_map(&:participants)
+ end
+ alias get_participants participants
+
+ # copies content of one object to the another
+ def self.copy_content(source, destination)
+ source.each do |each_element|
+ each_element.copy(destination.id)
+ end
+ end
+
+ # enum method for team clone operations
+ def self.team_operation
+ { inherit: 'inherit', bequeath: 'bequeath' }.freeze
+ end
+
+ # Get the response review map
+ def responses
+ participants.flat_map(&:responses)
+ end
+
+ # Delete the given team
+ def delete
+ TeamsUser.where(team_id: id).find_each(&:destroy)
+ node = TeamNode.find_by(node_object_id: id)
+ node.destroy if node
+ destroy
+ end
+
+ # Get the node type of the tree structure
+ def node_type
+ 'TeamNode'
+ end
+
+ # Get the names of the users
+ def author_names
+ names = []
+ users.each do |user|
+ names << user.fullname
+ end
+ names
+ end
- # TODO Team implementing Teams controller and model should implement this method better.
- # TODO partial implementation here just for the functionality needed for join_team_tequests controller
+ # Check if the user exist
+ def user?(user)
+ users.include? user
+ end
+
+ # Check if the current team is full?
def full?
+ return false if parent_id.nil? # course team, does not max_team_size
+
+ max_team_members = Assignment.find(parent_id).max_team_size
+ curr_team_size = Team.size(id)
+ curr_team_size >= max_team_members
+ end
+
+ # Add member to the team, changed to hash by E1776
+ def add_member(user, _assignment_id = nil)
+ raise "The user #{user.name} is already a member of the team #{name}" if user?(user)
+
+ can_add_member = false
+ unless full?
+ can_add_member = true
+ t_user = TeamsUser.create(user_id: user.id, team_id: id)
+ parent = TeamNode.find_by(node_object_id: id)
+ TeamUserNode.create(parent_id: parent.id, node_object_id: t_user.id)
+ add_participant(parent_id, user)
+ ExpertizaLogger.info LoggerMessage.new('Model:Team', user.name, "Added member to the team #{id}")
+ end
+ can_add_member
+ end
+
+ # Define the size of the team
+ def self.size(team_id)
+ #TeamsUser.where(team_id: team_id).count
+ count = 0
+ members = TeamsUser.where(team_id: team_id)
+ members.each do |member|
+ member_name = member.name
+ unless member_name.include?(' (Mentor)')
+ count = count + 1
+ end
+ end
+ count
+ end
+
+ # Copy method to copy this team
+ def copy_members(new_team)
+ members = TeamsUser.where(team_id: id)
+ members.each do |member|
+ t_user = TeamsUser.create(team_id: new_team.id, user_id: member.user_id)
+ parent = Object.const_get(parent_model).find(parent_id)
+ TeamUserNode.create(parent_id: parent.id, node_object_id: t_user.id)
+ end
+ end
+
+ # Check if the team exists
+ def self.check_for_existing(parent, name, team_type)
+ list = Object.const_get(team_type + 'Team').where(parent_id: parent.id, name: name)
+ raise TeamExistsError, "The team name #{name} is already in use." unless list.empty?
+ end
+
+ # Algorithm
+ # Start by adding single members to teams that are one member too small.
+ # Add two-member teams to teams that two members too small. etc.
+ def self.randomize_all_by_parent(parent, team_type, min_team_size)
+ participants = Participant.where(parent_id: parent.id, type: parent.class.to_s + 'Participant', can_mentor: [false, nil])
+ participants = participants.sort { rand(-1..1) }
+ users = participants.map { |p| User.find(p.user_id) }.to_a
+ # find teams still need team members and users who are not in any team
+ teams = Team.where(parent_id: parent.id, type: parent.class.to_s + 'Team').to_a
+ teams.each do |team|
+ TeamsUser.where(team_id: team.id).each do |teams_user|
+ users.delete(User.find(teams_user.user_id))
+ end
+ end
+ teams.reject! { |team| Team.size(team.id) >= min_team_size }
+ # sort teams that still need members by decreasing team size
+ teams.sort_by { |team| Team.size(team.id) }.reverse!
+ # insert users who are not in any team to teams still need team members
+ assign_single_users_to_teams(min_team_size, parent, teams, users) if !users.empty? && !teams.empty?
+ # If all the existing teams are fill to the min_team_size and we still have more users, create teams for them.
+ create_team_from_single_users(min_team_size, parent, team_type, users) unless users.empty?
+ end
+
+ # Creates teams from a list of users based on minimum team size
+ # Then assigns the created team to the parent object
+ def self.create_team_from_single_users(min_team_size, parent, team_type, users)
+ num_of_teams = users.length.fdiv(min_team_size).ceil
+ next_team_member_index = 0
+ (1..num_of_teams).to_a.each do |i|
+ team = Object.const_get(team_type + 'Team').create(name: 'Team_' + i.to_s, parent_id: parent.id)
+ TeamNode.create(parent_id: parent.id, node_object_id: team.id)
+ min_team_size.times do
+ break if next_team_member_index >= users.length
- max_participants ||= 3
- if participants.count >= max_participants
- true
+ user = users[next_team_member_index]
+ team.add_member(user, parent.id)
+ next_team_member_index += 1
+ end
+ end
+ end
+
+ # Assigns list of users to list of teams based on minimum team size
+ def self.assign_single_users_to_teams(min_team_size, parent, teams, users)
+ teams.each do |team|
+ curr_team_size = Team.size(team.id)
+ member_num_difference = min_team_size - curr_team_size
+ while member_num_difference > 0
+ team.add_member(users.first, parent.id)
+ users.delete(users.first)
+ member_num_difference -= 1
+ break if users.empty?
+ end
+ break if users.empty?
+ end
+ end
+
+ # Generate the team name
+ def self.generate_team_name(_team_name_prefix = '')
+ last_team = Team.where('name LIKE ?', "#{_team_name_prefix} Team_%")
+ .order("CAST(SUBSTRING(name, LENGTH('#{_team_name_prefix} Team_') + 1) AS UNSIGNED) DESC")
+ .first
+ counter = last_team ? last_team.name.scan(/\d+/).first.to_i + 1 : 1
+ team_name = "#{_team_name_prefix} Team_#{counter}"
+ team_name
+ end
+
+ # Extract team members from the csv and push to DB, changed to hash by E1776
+ def import_team_members(row_hash)
+ row_hash[:teammembers].each_with_index do |teammate, _index|
+ user = User.find_by(name: teammate.to_s)
+ if user.nil?
+ raise ImportError, "The user '#{teammate}' was not found. Create this user?"
+ else
+ add_member(user) if TeamsUser.find_by(team_id: id, user_id: user.id).nil?
+ end
+ end
+ end
+
+ # changed to hash by E1776
+ def self.import(row_hash, id, options, teamtype)
+ raise ArgumentError, 'Not enough fields on this line.' if row_hash.empty? || (row_hash[:teammembers].empty? && (options[:has_teamname] == 'true_first' || options[:has_teamname] == 'true_last')) || (row_hash[:teammembers].empty? && (options[:has_teamname] == 'true_first' || options[:has_teamname] == 'true_last'))
+
+ if options[:has_teamname] == 'true_first' || options[:has_teamname] == 'true_last'
+ name = row_hash[:teamname].to_s
+ team = where(['name =? && parent_id =?', name, id]).first
+ team_exists = !team.nil?
+ name = handle_duplicate(team, name, id, options[:handle_dups], teamtype)
else
- false
+ if teamtype.is_a?(CourseTeam)
+ name = generate_team_name(Course.find(id).name)
+ elsif teamtype.is_a?(AssignmentTeam)
+ name = generate_team_name(Assignment.find(id).name)
+ end
+ end
+ if name
+ team = Object.const_get(teamtype.to_s).create_team_and_node(id)
+ team.name = name
+ team.save
+ end
+
+ # insert team members into team unless team was pre-existing & we ignore duplicate teams
+
+ team.import_team_members(row_hash) unless team_exists && options[:handle_dups] == 'ignore'
+ end
+
+ # Handle existence of the duplicate team
+ def self.handle_duplicate(team, name, id, handle_dups, teamtype)
+ return name if team.nil? # no duplicate
+ return nil if handle_dups == 'ignore' # ignore: do not create the new team
+
+ if handle_dups == 'rename' # rename: rename new team
+ if teamtype.is_a?(CourseTeam)
+ return generate_team_name(Course.find(id).name)
+ elsif teamtype.is_a?(AssignmentTeam)
+ return generate_team_name(Assignment.find(id).name)
+ end
end
+ if handle_dups == 'replace' # replace: delete old team
+ team.delete
+ return name
+ else # handle_dups = "insert"
+ return nil
+ end
+ end
+
+ # Export the teams to csv
+ def self.export(csv, parent_id, options, teamtype)
+ if teamtype.is_a?(CourseTeam)
+ teams = CourseTeam.where(parent_id: parent_id)
+ elsif teamtype.is_a?(AssignmentTeam)
+ teams = AssignmentTeam.where(parent_id: parent_id)
+ end
+ teams.each do |team|
+ output = []
+ output.push(team.name)
+ if options[:team_name] == 'false'
+ team_members = TeamsUser.where(team_id: team.id)
+ team_members.each do |user|
+ output.push(user.name)
+ end
+ end
+ csv << output
+ end
+ csv
+ end
+
+ # Create the team with corresponding tree node
+ def self.create_team_and_node(id)
+ parent = parent_model id # current_task will be either a course object or an assignment object.
+ team_name = Team.generate_team_name(parent.name)
+ team = create(name: team_name, parent_id: id)
+ # new teamnode will have current_task.id as parent_id and team_id as node_object_id.
+ TeamNode.create(parent_id: id, node_object_id: team.id)
+ ExpertizaLogger.info LoggerMessage.new('Model:Team', '', "New TeamNode created with teamname #{team_name}")
+ team
+ end
+
+ # E1991 : This method allows us to generate
+ # team names based on whether anonymized view
+ # is set or not. The logic is similar to
+ # existing logic of User model.
+ def name(ip_address = nil)
+ if User.anonymized_view?(ip_address)
+ return "Anonymized_Team_#{self[:id]}"
+ else
+ return self[:name]
+ end
+ end
+
+ # REFACTOR END:: class methods import export moved from course_team & assignment_team to here
+
+ # Create the team with corresponding tree node and given users
+ def self.create_team_with_users(parent_id, user_ids)
+ team = create_team_and_node(parent_id)
+
+ user_ids.each do |user_id|
+ remove_user_from_previous_team(parent_id, user_id)
+
+ # Create new team_user and team_user node
+ team.add_member(User.find(user_id))
+ end
+ team
+ end
+
+ # Removes the specified user from any team of the specified assignment
+ def self.remove_user_from_previous_team(parent_id, user_id)
+ team_user = TeamsUser.where(user_id: user_id).find { |team_user_obj| team_user_obj.team.parent_id == parent_id }
+ begin
+ team_user.destroy
+ rescue StandardError
+ nil
+ end
+ end
+
+ def self.find_team_users(assignment_id, user_id)
+ TeamsUser.joins('INNER JOIN teams ON teams_users.team_id = teams.id')
+ .select('teams.id as t_id')
+ .where('teams.parent_id = ? and teams_users.user_id = ?', assignment_id, user_id)
end
end
\ No newline at end of file
diff --git a/app/models/teams_participants.rb b/app/models/teams_participants.rb
index 9e1768b94..a1e4b53e3 100644
--- a/app/models/teams_participants.rb
+++ b/app/models/teams_participants.rb
@@ -2,22 +2,62 @@ class TeamsUser < ApplicationRecord
belongs_to :user
belongs_to :team
+ # Returns the user's name. If an IP address is provided, it may influence the name retrieval logic.
def name(ip_address = nil)
- name = user.name(ip_address)
+ user.name(ip_address)
end
- def get_team_members(team_id)
- team_members = TeamsUser.where('team_id = ?', team_id)
- user_ids = team_members.pluck(:user_id)
- users = User.where(id: user_ids)
+ # Retrieves all team members for a given team ID as a collection of User objects.
+ # Allows optional exclusion of certain roles.
+ def self.get_team_members(team_id, excluded_roles: [])
+ users = where(team_id: team_id).includes(:user).map(&:user)
+ return users if excluded_roles.empty?
- return users
+ # Exclude users with specific roles, if any
+ users.reject { |user| excluded_roles.include?(user.role) }
end
- # Removes entry in the TeamUsers table for the given user and given team id
- def self.remove_team(user_id, team_id)
- team_user = TeamsUser.where('user_id = ? and team_id = ?', user_id, team_id).first
- team_user&.destroy
+ # Adds a user to a team. Raises an error if the user is already on the team.
+ # Returns the created TeamsUser object if successful.
+ def self.add_to_team(user_id, team_id)
+ # Check if the user is already a team member
+ if where(user_id: user_id, team_id: team_id).exists?
+ raise "The user is already a member of the team."
+ end
+
+ # Create the association
+ create!(user_id: user_id, team_id: team_id)
+ rescue ActiveRecord::RecordInvalid => e
+ raise "Failed to add user to team: #{e.message}"
end
+ # Removes a user's association with a team. Raises an error if the association does not exist.
+ def self.remove_from_team(user_id, team_id)
+ team_user = find_by(user_id: user_id, team_id: team_id)
+ raise "The user is not a member of this team." if team_user.nil?
+
+ team_user.destroy
+ rescue StandardError => e
+ raise "Failed to remove user from team: #{e.message}"
+ end
+
+ # Transfers a user from one team to another within the same context.
+ # Ensures that the user is removed from the previous team before adding to the new one.
+ def self.transfer_user_to_team(user_id, old_team_id, new_team_id)
+ remove_from_team(user_id, old_team_id)
+ add_to_team(user_id, new_team_id)
+ rescue StandardError => e
+ raise "Failed to transfer user between teams: #{e.message}"
+ end
+
+ # Checks if a user is already on a team.
+ def self.user_on_team?(user_id, team_id)
+ where(user_id: user_id, team_id: team_id).exists?
+ end
+
+ # Retrieves all teams for a given user as a collection of Team objects.
+ def self.get_teams_for_user(user_id)
+ team_ids = where(user_id: user_id).pluck(:team_id)
+ Team.where(id: team_ids)
+ end
end
From a5534356539f1c776691b786f785a866c39a7356 Mon Sep 17 00:00:00 2001
From: manideepika21
Date: Sat, 30 Nov 2024 12:31:08 -0500
Subject: [PATCH 09/18] Moved teams_users model from expertiza to
reimplementation-back-end repository
---
app/models/teams_participants.rb | 101 +++++++++++++++++--------------
1 file changed, 57 insertions(+), 44 deletions(-)
diff --git a/app/models/teams_participants.rb b/app/models/teams_participants.rb
index a1e4b53e3..d2086e969 100644
--- a/app/models/teams_participants.rb
+++ b/app/models/teams_participants.rb
@@ -1,63 +1,76 @@
class TeamsUser < ApplicationRecord
belongs_to :user
belongs_to :team
+ has_one :team_user_node, foreign_key: 'node_object_id', dependent: :destroy
+ has_paper_trail
+ # attr_accessible :user_id, :team_id # unnecessary protected attributes
- # Returns the user's name. If an IP address is provided, it may influence the name retrieval logic.
def name(ip_address = nil)
- user.name(ip_address)
- end
+ name = user.name(ip_address)
- # Retrieves all team members for a given team ID as a collection of User objects.
- # Allows optional exclusion of certain roles.
- def self.get_team_members(team_id, excluded_roles: [])
- users = where(team_id: team_id).includes(:user).map(&:user)
- return users if excluded_roles.empty?
+ # E2115 Mentor Management
+ # Indicate that someone is a Mentor in the UI. The view code is
+ # often hard to follow, and this is the best place we could find
+ # for this to go.
+ name += ' (Mentor)' if MentorManagement.user_a_mentor?(user)
+ name
+ end
- # Exclude users with specific roles, if any
- users.reject { |user| excluded_roles.include?(user.role) }
+ def delete
+ TeamUserNode.find_by(node_object_id: id).destroy
+ team = self.team
+ destroy
+ team.delete if team.teams_users.empty?
end
- # Adds a user to a team. Raises an error if the user is already on the team.
- # Returns the created TeamsUser object if successful.
- def self.add_to_team(user_id, team_id)
- # Check if the user is already a team member
- if where(user_id: user_id, team_id: team_id).exists?
- raise "The user is already a member of the team."
- end
+ def get_team_members(team_id); end
- # Create the association
- create!(user_id: user_id, team_id: team_id)
- rescue ActiveRecord::RecordInvalid => e
- raise "Failed to add user to team: #{e.message}"
+ # Removes entry in the TeamUsers table for the given user and given team id
+ def self.remove_team(user_id, team_id)
+ team_user = TeamsUser.where('user_id = ? and team_id = ?', user_id, team_id).first
+ team_user&.destroy
end
- # Removes a user's association with a team. Raises an error if the association does not exist.
- def self.remove_from_team(user_id, team_id)
- team_user = find_by(user_id: user_id, team_id: team_id)
- raise "The user is not a member of this team." if team_user.nil?
-
- team_user.destroy
- rescue StandardError => e
- raise "Failed to remove user from team: #{e.message}"
+ # Returns the first entry in the TeamUsers table for a given team id
+ def self.first_by_team_id(team_id)
+ TeamsUser.where('team_id = ?', team_id).first
end
- # Transfers a user from one team to another within the same context.
- # Ensures that the user is removed from the previous team before adding to the new one.
- def self.transfer_user_to_team(user_id, old_team_id, new_team_id)
- remove_from_team(user_id, old_team_id)
- add_to_team(user_id, new_team_id)
- rescue StandardError => e
- raise "Failed to transfer user between teams: #{e.message}"
+ # Determines whether a team is empty of not
+ def self.team_empty?(team_id)
+ team_members = TeamsUser.where('team_id = ?', team_id)
+ team_members.blank?
end
- # Checks if a user is already on a team.
- def self.user_on_team?(user_id, team_id)
- where(user_id: user_id, team_id: team_id).exists?
+ # Add member to the team they were invited to and accepted the invite for
+ def self.add_member_to_invited_team(invitee_user_id, invited_user_id, assignment_id)
+ can_add_member = false
+ users_teams = TeamsUser.where(['user_id = ?', invitee_user_id])
+ users_teams.each do |team|
+ new_team = AssignmentTeam.where(['id = ? and parent_id = ?', team.team_id, assignment_id]).first
+ unless new_team.nil?
+ can_add_member = new_team.add_member(User.find(invited_user_id), assignment_id)
+ end
+ end
+ can_add_member
end
- # Retrieves all teams for a given user as a collection of Team objects.
- def self.get_teams_for_user(user_id)
- team_ids = where(user_id: user_id).pluck(:team_id)
- Team.where(id: team_ids)
+ # 2015-5-27 [zhewei]:
+ # We just remove the topic_id field from the participants table.
+ def self.team_id(assignment_id, user_id)
+ # team_id variable represents the team_id for this user in this assignment
+ team_id = nil
+ teams_users = TeamsUser.where(user_id: user_id)
+ teams_users.each do |teams_user|
+ if teams_user.team_id == nil
+ next
+ end
+ team = Team.find(teams_user.team_id)
+ if team.parent_id == assignment_id
+ team_id = teams_user.team_id
+ break
+ end
+ end
+ team_id
end
-end
+end
\ No newline at end of file
From 638ed3135e39d617a348c8faaf1b16c73ff036d1 Mon Sep 17 00:00:00 2001
From: bhuvan chandra kurra
Date: Mon, 2 Dec 2024 21:19:47 -0500
Subject: [PATCH 10/18] modified assignments model to move the assignment
specific methods to this model
---
app/models/assignment.rb | 60 ++++++++++++++++++++++++++++++++++++----
1 file changed, 54 insertions(+), 6 deletions(-)
diff --git a/app/models/assignment.rb b/app/models/assignment.rb
index 45e8d2acf..148ffed46 100644
--- a/app/models/assignment.rb
+++ b/app/models/assignment.rb
@@ -11,6 +11,7 @@ class Assignment < ApplicationRecord
has_many :sign_up_topics , class_name: 'SignUpTopic', foreign_key: 'assignment_id', dependent: :destroy
belongs_to :course, optional: true
belongs_to :instructor, class_name: 'User', inverse_of: :assignments
+ validates :max_team_size, numericality: { only_integer: true, greater_than_or_equal_to: 1 }, allow_nil: true
#This method return the value of the has_badge field for the given assignment object.
attr_accessor :title, :description, :has_badge, :enable_pair_programming, :is_calibrated, :staggered_deadline
@@ -22,10 +23,11 @@ def review_questionnaire_id
def teams?
@has_teams ||= teams.any?
end
+
def num_review_rounds
rounds_of_reviews
end
-
+
# Add a participant to the assignment based on the provided user_id.
# This method first finds the User with the given user_id. If the user does not exist, it raises an error.
# It then checks if the user is already a participant in the assignment. If so, it raises an error.
@@ -79,8 +81,6 @@ def remove_assignment_from_course
self
end
-
-
# Assign a course to the assignment based on the provided course_id.
# If the assignment already belongs to the specified course, an error is raised.
# Returns the modified assignment object with the updated course assignment.
@@ -98,7 +98,6 @@ def assign_course(course_id)
assignment
end
-
# Create a copy of the assignment, including its name, instructor, and course assignment.
# The new assignment is named "Copy of [original assignment name]".
# Returns the newly created assignment object, which is a copy of the original assignment.
@@ -117,6 +116,7 @@ def copy
copied_assignment
end
+
def is_calibrated?
is_calibrated
end
@@ -133,7 +133,6 @@ def staggered_and_no_topic?(topic_id)
staggered_deadline? && topic_id.nil?
end
-
#This method return the value of the has_topics field for the given assignment object.
# has_topics is of boolean type and is set true if there is any topic associated with the assignment.
def topics?
@@ -183,7 +182,6 @@ def valid_num_review(review_type)
end
end
-
#This method check if for the given assignment,different type of rubrics are used in different round.
# Checks if for the given assignment any questionnaire is present with used_in_round field not nil.
# Returns a bolean value whether such questionnaire is present.
@@ -193,6 +191,56 @@ def varying_rubrics_by_round?
rubric_with_round.present?
end
+
+#E2479
+# Checks if a specific user is already part of any team for this assignment.
+# This method queries the teams associated with this assignment and checks if the user is a member.
+# Params:
+# - user: The user to check for team membership.
+# Returns:
+# - true if the user is part of any team for this assignment.
+# - false otherwise.
+def is_participant_on_team?(user)
+ # Join the teams and team users tables to check if a record exists for the user.
+ teams.joins(:teams_users).exists?(teams_users: { user_id: user.id })
+end
+
+# Validates if a user is eligible to join a team for the current assignment.
+# This method ensures that:
+# - The user is not already part of another team for this assignment.
+# - The user is a valid participant in the assignment.
+# Params:
+# - user: The user to validate for team membership.
+# Returns:
+# - A hash indicating the validation result:
+# - { success: true } if the user can join the team.
+# - { success: false, error: "Reason for failure" } if the user cannot join the team.
+def validate_team_participant_for_assignment?(user)
+ # Check if the user is already part of a team for this assignment.
+ if is_user_on_team?(user)
+ { success: false, error: "This user is already assigned to a team for this assignment" }
+
+ # Check if the user is a registered participant in the assignment.
+ elsif AssignmentParticipant.find_by(user_id: user.id, parent_id: assignment_id).nil?
+ { success: false, error: "#{user.name} is not a participant in this assignment" }
+
+ # If both checks pass, the user is eligible to join the team.
+ else
+ { success: true }
+ end
+end
+
+# Retrieves all courses assigned to a specific instructor.
+# This method fetches courses for which the given user is listed as the instructor.
+# The courses are ordered alphabetically by their names for consistent display.
+# Params:
+# - user: The instructor user whose courses are to be fetched.
+# Returns:
+# - An ActiveRecord relation containing the courses assigned to the instructor.
+def self.fetch_courses_for_instructor(user)
+ # Query for courses where the instructor ID matches the user's ID, ordered by course name.
+ Course.where(instructor_id: user.id).order(:name)
+end
end
\ No newline at end of file
From 2d4b9804d40880841d4ee94d9db71f96c63e6e87 Mon Sep 17 00:00:00 2001
From: bhuvan chandra kurra
Date: Mon, 2 Dec 2024 21:22:58 -0500
Subject: [PATCH 11/18] modified course model to move the course specific
methods to this model
---
app/models/course.rb | 28 +++++++++++++++++++++++++++-
1 file changed, 27 insertions(+), 1 deletion(-)
diff --git a/app/models/course.rb b/app/models/course.rb
index 9e70ccf7d..f2874d43d 100644
--- a/app/models/course.rb
+++ b/app/models/course.rb
@@ -11,7 +11,7 @@ def path
raise 'Path can not be created as the course must be associated with an instructor.' if instructor_id.nil?
Rails.root + '/' + Institution.find(institution_id).name.gsub(" ", "") + '/' + User.find(instructor_id).name.gsub(" ", "") + '/' + directory_path + '/'
end
-
+
# Add a Teaching Assistant to the course
def add_ta(user)
if user.nil?
@@ -49,4 +49,30 @@ def copy_course
new_course.name += '_copy'
new_course.save
end
+ #E2479
+ # Checks if a user is eligible to join a specific team for a course.
+# This method ensures that:
+# - The user is not already a member of another team for the course.
+# - The user is a valid participant in the course.
+# Params:
+# - user: The user to be validated for team membership.
+# Returns:
+# - A hash indicating success or failure:
+# - { success: true } if the user can be added to the team.
+# - { success: false, error: "Reason for failure" } if the user cannot be added.
+def valid_team_participant?(user)
+ # Check if the user is already a member of another team for the same course.
+ if already_on_team?(user)
+ { success: false, error: "This user is already assigned to a team for this course" }
+
+ # Check if the user is a participant in the course associated with this team.
+ elsif CourseParticipant.find_by(user_id: user.id, parent_id: course_id).nil?
+ { success: false, error: "#{user.name} is not a participant in this course" }
+
+ # If both checks pass, the user is eligible to join the team.
+ else
+ { success: true }
+ end
+end
+
end
\ No newline at end of file
From f97c630072c606b25b1317e435f0b97733fd10a8 Mon Sep 17 00:00:00 2001
From: bhuvan chandra kurra
Date: Mon, 2 Dec 2024 21:28:45 -0500
Subject: [PATCH 12/18] modified teams participants model to move the teams
participants methods to this model
---
app/models/teams_participants.rb | 109 +++++++++++++++++--------------
1 file changed, 59 insertions(+), 50 deletions(-)
diff --git a/app/models/teams_participants.rb b/app/models/teams_participants.rb
index a1e4b53e3..9d4b49fb7 100644
--- a/app/models/teams_participants.rb
+++ b/app/models/teams_participants.rb
@@ -1,63 +1,72 @@
-class TeamsUser < ApplicationRecord
+class TeamsParticipant < ApplicationRecord
belongs_to :user
belongs_to :team
+ has_one :team_user_node, foreign_key: 'node_object_id', dependent: :destroy
+ has_paper_trail
- # Returns the user's name. If an IP address is provided, it may influence the name retrieval logic.
- def name(ip_address = nil)
- user.name(ip_address)
- end
-
- # Retrieves all team members for a given team ID as a collection of User objects.
- # Allows optional exclusion of certain roles.
- def self.get_team_members(team_id, excluded_roles: [])
- users = where(team_id: team_id).includes(:user).map(&:user)
- return users if excluded_roles.empty?
+ def get_team_members(team_id)
+ team_members = TeamsUser.where('team_id = ?', team_id)
+ user_ids = team_members.pluck(:user_id)
+ users = User.where(id: user_ids)
- # Exclude users with specific roles, if any
- users.reject { |user| excluded_roles.include?(user.role) }
+ return users
end
- # Adds a user to a team. Raises an error if the user is already on the team.
- # Returns the created TeamsUser object if successful.
- def self.add_to_team(user_id, team_id)
- # Check if the user is already a team member
- if where(user_id: user_id, team_id: team_id).exists?
- raise "The user is already a member of the team."
- end
-
- # Create the association
- create!(user_id: user_id, team_id: team_id)
- rescue ActiveRecord::RecordInvalid => e
- raise "Failed to add user to team: #{e.message}"
+ # Removes entry in the TeamUsers table for the given user and given team id
+ def self.remove_team(user_id, team_id)
+ team_user = TeamsUser.where('user_id = ? and team_id = ?', user_id, team_id).first
+ team_user&.destroy
end
- # Removes a user's association with a team. Raises an error if the association does not exist.
- def self.remove_from_team(user_id, team_id)
- team_user = find_by(user_id: user_id, team_id: team_id)
- raise "The user is not a member of this team." if team_user.nil?
+#E2479
+# Retrieves the name of the user associated with the team member.
+# Optionally appends ' (Mentor)' if the user is a mentor.
+# Params:
+# - ip_address (optional): The IP address of the user (used for logging or contextual name resolution).
+# Returns:
+# - The name of the user, with '(Mentor)' appended if the user is a mentor.
+def display_name(ip_address = nil)
+ participant_name = user.name(ip_address)
+ participant_name += ' (Mentor)' if MentorManagement.user_a_mentor?(user)
+ participant_name
+end
- team_user.destroy
- rescue StandardError => e
- raise "Failed to remove user from team: #{e.message}"
- end
+# Deletes multiple team members (identified by their IDs) in bulk.
+# This method is used for efficient removal of multiple TeamsUser records.
+# Params:
+# - team_user_ids: An array of IDs of the TeamsUser records to be deleted.
+# Returns:
+# - The number of records deleted (implicit return from destroy_all).
+def self.bulk_delete(team_user_ids)
+ # Delete all TeamsUser records matching the provided IDs.
+ where(id: team_user_ids).destroy_all
+end
- # Transfers a user from one team to another within the same context.
- # Ensures that the user is removed from the previous team before adding to the new one.
- def self.transfer_user_to_team(user_id, old_team_id, new_team_id)
- remove_from_team(user_id, old_team_id)
- add_to_team(user_id, new_team_id)
- rescue StandardError => e
- raise "Failed to transfer user between teams: #{e.message}"
- end
+# Checks whether a specific user is a member of a given team.
+# Params:
+# - user_id: The ID of the user to check.
+# - team_id: The ID of the team to check for membership.
+# Returns:
+# - true if the user is a member of the team.
+# - false otherwise.
+def self.participant_part_of_team?(user_id, team_id)
+ # Check if a TeamsUser record exists with the specified user and team IDs.
+ exists?(user_id: user_id, team_id: team_id)
+end
+
+# Checks whether a team is empty (i.e., has no members).
+# Params:
+# - team_id: The ID of the team to check.
+# Returns:
+# - true if the team has no members.
+# - false otherwise.
+def self.is_team_empty?(team_id)
+ # Retrieve all members of the team.
+ team_members = TeamsUser.where('team_id = ?', team_id)
+
+ # Return true if the team has no members; false otherwise.
+ team_members.blank?
+end
- # Checks if a user is already on a team.
- def self.user_on_team?(user_id, team_id)
- where(user_id: user_id, team_id: team_id).exists?
- end
- # Retrieves all teams for a given user as a collection of Team objects.
- def self.get_teams_for_user(user_id)
- team_ids = where(user_id: user_id).pluck(:team_id)
- Team.where(id: team_ids)
- end
end
From c3893ef54e283504ba060c52422e268d2ddcb726 Mon Sep 17 00:00:00 2001
From: manideepika21
Date: Mon, 2 Dec 2024 21:30:27 -0500
Subject: [PATCH 13/18] Modified teams_users_controller to adhere to DRY
principles and refactores the code with detailed comments and the variable
names
---
.../teams_participants_controller.rb | 176 ++++---
app/models/assignment.rb | 85 ++--
app/models/assignment_participant.rb | 9 +-
app/models/course.rb | 147 +++---
app/models/mentor_management.rb | 122 +++++
app/models/mentored_team.rb | 27 ++
app/models/team.rb | 447 ++++++------------
app/models/teams_participants.rb | 110 +++--
8 files changed, 559 insertions(+), 564 deletions(-)
create mode 100644 app/models/mentor_management.rb
create mode 100644 app/models/mentored_team.rb
diff --git a/app/controllers/teams_participants_controller.rb b/app/controllers/teams_participants_controller.rb
index e445a0345..c6796c102 100644
--- a/app/controllers/teams_participants_controller.rb
+++ b/app/controllers/teams_participants_controller.rb
@@ -1,8 +1,8 @@
class TeamsParticipantsController < ApplicationController
include AuthorizationHelper
+ # Determines if the current user is allowed to perform the requested action.
def action_allowed?
- # Allow duty updation for a team if current user is student, else require TA or above Privileges.
if %w[update_duties].include? params[:action]
current_user_has_student_privileges?
else
@@ -10,103 +10,127 @@ def action_allowed?
end
end
+ # Fetches and renders an auto-complete list of possible team members based on a partial name input.
def auto_complete_for_user_name
- team = Team.find(session[:team_id])
- @users = team.get_possible_team_members(params[:user][:name])
- render inline: "<%= auto_complete_result @users, 'name' %>", layout: false
+ # Fetch the current team using the session-stored `team_id`.
+ current_team = Team.find(session[:team_id])
+
+ # Fetch potential members for the team based on the input name.
+ @potential_team_members = current_team.get_possible_team_members(params[:user][:name])
+
+ # Render the autocomplete suggestions.
+ render inline: "<%= auto_complete_result @potential_team_members, 'name' %>", layout: false
end
- # Example of duties: manager, designer, programmer, tester. Finds TeamsUser and save preferred Duty
+ # Updates the duty (role) assigned to a participant in a team.
def update_duties
- team_user = TeamsUser.find(params[:teams_user_id])
- team_user.update_attribute(:duty_id, params[:teams_user]['duty_id'])
+ # Find the team member relationship using the provided ID.
+ team_member_relationship = TeamsUser.find(params[:teams_user_id])
+
+ # Update the duty of the team member.
+ team_member_relationship.update_attribute(:duty_id, params[:teams_user]['duty_id'])
+
+ # Redirect to the participant's team view page.
redirect_to controller: 'student_teams', action: 'view', student_id: params[:participant_id]
end
+ # Displays a paginated list of all participants in a specific team.
def list
- @team = Team.find(params[:id])
- @assignment = Assignment.find(@team.parent_id)
- @teams_users = TeamsUser.page(params[:page]).per_page(10).where(['team_id = ?', params[:id]])
+ # Fetch the team based on the provided ID.
+ current_team = Team.find(params[:id])
+
+ # Retrieve the associated assignment or course for the team.
+ associated_assignment_or_course = Assignment.find(current_team.parent_id)
+
+ # Query and paginate participants of the current team.
+ @team_participants = TeamsUser.page(params[:page]).per_page(10).where(team_id: current_team.id)
+
+ @team = current_team
+ @assignment = associated_assignment_or_course
end
+ # Renders the form for adding a new participant to a team.
def new
+ # Fetch the team for which a participant is to be added.
@team = Team.find(params[:id])
end
+ # Adds a new participant to a team after validation.
def create
- user = User.find_by(name: params[:user][:name].strip)
- unless user
- urlCreate = url_for controller: 'users', action: 'new'
- flash[:error] = "\"#{params[:user][:name].strip}\" is not defined. Please create this user before continuing."
- end
+ # Find the user by their name from the input.
+ participant = find_user_by_name
+
+ # Fetch the team using the provided ID.
+ current_team = find_team_by_id
+
+ # Return early if validation fails.
+ return unless validate_participant_and_team(participant, current_team)
+
+ # Add the participant to the team.
+ add_participant_to_team(participant, current_team)
- team = Team.find(params[:id])
- unless user.nil?
- if team.is_a?(AssignmentTeam)
- assignment = Assignment.find(team.parent_id)
- if assignment.user_on_team?(user)
- flash[:error] = "This user is already assigned to a team for this assignment"
- redirect_back fallback_location: root_path
- return
- end
- if AssignmentParticipant.find_by(user_id: user.id, parent_id: assignment.id).nil?
- urlAssignmentParticipantList = url_for controller: 'participants', action: 'list', id: assignment.id, model: 'Assignment', authorization: 'participant'
- flash[:error] = "\"#{user.name}\" is not a participant of the current assignment. Please add this user before continuing."
- else
- begin
- add_member_return = team.add_member(user, team.parent_id)
- rescue
- flash[:error] = "The user #{user.name} is already a member of the team #{team.name}"
- redirect_back fallback_location: root_path
- return
- end
- flash[:error] = 'This team already has the maximum number of members.' if add_member_return == false
- end
- else # CourseTeam
- course = Course.find(team.parent_id)
- if course.user_on_team?(user)
- flash[:error] = "This user is already assigned to a team for this course"
- redirect_back fallback_location: root_path
- return
- end
- if CourseParticipant.find_by(user_id: user.id, parent_id: course.id).nil?
- urlCourseParticipantList = url_for controller: 'participants', action: 'list', id: course.id, model: 'Course', authorization: 'participant'
- flash[:error] = "\"#{user.name}\" is not a participant of the current course. Please add this user before continuing."
- else
- begin
- add_member_return = team.add_member(user, team.parent_id)
- rescue
- flash[:error] = "The user #{user.name} is already a member of the team #{team.name}"
- redirect_back fallback_location: root_path
- return
- end
- flash[:error] = 'This team already has the maximum number of members.' if add_member_return == false
- if add_member_return
- @teams_user = TeamsUser.last
- undo_link("The team user \"#{user.name}\" has been successfully added to \"#{team.name}\".")
- end
- end
- end
+ # Redirect to the list of teams for the parent assignment or course.
+ redirect_to controller: 'teams', action: 'list', id: current_team.parent_id
+ end
+
+ private
+
+ # Helper method to find a user by their name.
+ def find_user_by_name
+ # Locate the user by their name.
+ participant = User.find_by(name: params[:user][:name].strip)
+
+ # Display an error if the user is not found.
+ unless participant
+ flash[:error] = user_not_found_error
+ redirect_back fallback_location: root_path
end
+ participant
+ end
+
+ # Helper method to fetch a team by its ID.
+ def find_team_by_id
+ Team.find(params[:id])
+ end
+
+ # Validates whether a participant can be added to the given team.
+ def validate_participant_and_team(participant, team)
+ # Check if the participant is valid for the team type.
+ validation_result = if team.is_a?(AssignmentTeam)
+ Assignment.find(team.parent_id).valid_team_member?(participant)
+ else
+ Course.find(team.parent_id).valid_team_member?(participant)
+ end
- redirect_to controller: 'teams', action: 'list', id: team.parent_id
+ # Handle validation errors if any.
+ if validation_result[:success]
+ true
+ else
+ flash[:error] = validation_result[:error]
+ redirect_back fallback_location: root_path
+ false
+ end
end
- def delete
- @teams_user = TeamsUser.find(params[:id])
- parent_id = Team.find(@teams_user.team_id).parent_id
- @user = User.find(@teams_user.user_id)
- @teams_user.destroy
- undo_link("The team user \"#{@user.name}\" has been successfully removed. ")
- redirect_to controller: 'teams', action: 'list', id: parent_id
+ # Adds the participant to the team while handling constraints.
+ def add_participant_to_team(participant, team)
+ # Add the participant to the team and handle the outcome.
+ addition_result = team.add_member_with_handling(participant, team.parent_id)
+ handle_addition_result(participant, team, addition_result)
end
- def delete_selected
- params[:item].each do |item_id|
- team_user = TeamsUser.find(item_id).first
- team_user.destroy
+ # Handles the result of adding a participant to the team.
+ def handle_addition_result(participant, team, addition_result)
+ if addition_result == false
+ flash[:error] = 'This team already has the maximum number of members.'
+ else
+ undo_link("The participant \"#{participant.name}\" has been successfully added to \"#{team.name}\".")
end
+ end
- redirect_to action: 'list', id: params[:id]
+ # Generates an error message when a user is not found.
+ def user_not_found_error
+ new_user_url = url_for controller: 'users', action: 'new'
+ "\"#{params[:user][:name].strip}\" is not defined. Please create this user before continuing."
end
-end
\ No newline at end of file
+end
diff --git a/app/models/assignment.rb b/app/models/assignment.rb
index e4b6ed13b..9b3167c03 100644
--- a/app/models/assignment.rb
+++ b/app/models/assignment.rb
@@ -11,6 +11,7 @@ class Assignment < ApplicationRecord
has_many :sign_up_topics , class_name: 'SignUpTopic', foreign_key: 'assignment_id', dependent: :destroy
belongs_to :course, optional: true
belongs_to :instructor, class_name: 'User', inverse_of: :assignments
+ validates :max_team_size, numericality: { only_integer: true, greater_than_or_equal_to: 1 }, allow_nil: true
#This method return the value of the has_badge field for the given assignment object.
attr_accessor :title, :description, :has_badge, :enable_pair_programming, :is_calibrated, :staggered_deadline
@@ -22,36 +23,10 @@ def review_questionnaire_id
def teams?
@has_teams ||= teams.any?
end
+
def num_review_rounds
rounds_of_reviews
end
-
- # Check if a user is on a team in this assignment
- def user_on_team?(user)
- teams.joins(:users).exists?(users: { id: user.id })
- end
-
- # Add a user to a team for this assignment
- def add_user_to_team(user, team_id)
- team = teams.find_by(id: team_id)
- raise "Team not found in this assignment." unless team
-
- if user_on_team?(user)
- raise "The user #{user.name} is already assigned to a team for this assignment."
- end
-
- team.add_member(user, id)
- rescue StandardError => e
- raise "Failed to add user to team: #{e.message}"
- end
-
- # Validate if a user is a participant of this assignment
- def validate_participant(user)
- participant = participants.find_by(user_id: user.id)
- raise "User #{user.name} is not a participant of this assignment." unless participant
-
- participant
- end
# Add a participant to the assignment based on the provided user_id.
# This method first finds the User with the given user_id. If the user does not exist, it raises an error.
@@ -106,8 +81,6 @@ def remove_assignment_from_course
self
end
-
-
# Assign a course to the assignment based on the provided course_id.
# If the assignment already belongs to the specified course, an error is raised.
# Returns the modified assignment object with the updated course assignment.
@@ -125,7 +98,6 @@ def assign_course(course_id)
assignment
end
-
# Create a copy of the assignment, including its name, instructor, and course assignment.
# The new assignment is named "Copy of [original assignment name]".
# Returns the newly created assignment object, which is a copy of the original assignment.
@@ -144,6 +116,7 @@ def copy
copied_assignment
end
+
def is_calibrated?
is_calibrated
end
@@ -160,7 +133,6 @@ def staggered_and_no_topic?(topic_id)
staggered_deadline? && topic_id.nil?
end
-
#This method return the value of the has_topics field for the given assignment object.
# has_topics is of boolean type and is set true if there is any topic associated with the assignment.
def topics?
@@ -210,7 +182,6 @@ def valid_num_review(review_type)
end
end
-
#This method check if for the given assignment,different type of rubrics are used in different round.
# Checks if for the given assignment any questionnaire is present with used_in_round field not nil.
# Returns a bolean value whether such questionnaire is present.
@@ -219,7 +190,55 @@ def varying_rubrics_by_round?
# Check if any rubric has a specified round
rubric_with_round.present?
end
-
+ #E2479
+ # Checks if a specific user is already part of any team for this assignment.
+# This method queries the teams associated with this assignment and checks if the user is a member.
+# Params:
+# - user: The user to check for team membership.
+# Returns:
+# - true if the user is part of any team for this assignment.
+# - false otherwise.
+def is_participant_on_team?(user)
+ # Join the teams and team users tables to check if a record exists for the user.
+ teams.joins(:teams_users).exists?(teams_users: { user_id: user.id })
+end
+
+# Validates if a user is eligible to join a team for the current assignment.
+# This method ensures that:
+# - The user is not already part of another team for this assignment.
+# - The user is a valid participant in the assignment.
+# Params:
+# - user: The user to validate for team membership.
+# Returns:
+# - A hash indicating the validation result:
+# - { success: true } if the user can join the team.
+# - { success: false, error: "Reason for failure" } if the user cannot join the team.
+def validate_team_participant_for_assignment?(user)
+ # Check if the user is already part of a team for this assignment.
+ if is_user_on_team?(user)
+ { success: false, error: "This user is already assigned to a team for this assignment" }
+
+ # Check if the user is a registered participant in the assignment.
+ elsif AssignmentParticipant.find_by(user_id: user.id, parent_id: assignment_id).nil?
+ { success: false, error: "#{user.name} is not a participant in this assignment" }
+
+ # If both checks pass, the user is eligible to join the team.
+ else
+ { success: true }
+ end
+end
+
+# Retrieves all courses assigned to a specific instructor.
+# This method fetches courses for which the given user is listed as the instructor.
+# The courses are ordered alphabetically by their names for consistent display.
+# Params:
+# - user: The instructor user whose courses are to be fetched.
+# Returns:
+# - An ActiveRecord relation containing the courses assigned to the instructor.
+def self.fetch_courses_for_instructor(user)
+ # Query for courses where the instructor ID matches the user's ID, ordered by course name.
+ Course.where(instructor_id: user.id).order(:name)
+end
end
\ No newline at end of file
diff --git a/app/models/assignment_participant.rb b/app/models/assignment_participant.rb
index 10ca53b5d..1114152ad 100644
--- a/app/models/assignment_participant.rb
+++ b/app/models/assignment_participant.rb
@@ -5,7 +5,6 @@ class AssignmentParticipant < Participant
belongs_to :user
validates :handle, presence: true
-
def set_handle
self.handle = if user.handle.nil? || (user.handle == '')
user.name
@@ -16,5 +15,13 @@ def set_handle
end
self.save
end
+ #E2479
+ def team
+ AssignmentTeam.team(self)
+ end
+
+ def team_user
+ TeamsUser.where(team_id: team.id, user_id: user_id).first if team
+ end
end
\ No newline at end of file
diff --git a/app/models/course.rb b/app/models/course.rb
index c7ed5b506..f2874d43d 100644
--- a/app/models/course.rb
+++ b/app/models/course.rb
@@ -1,101 +1,78 @@
class Course < ApplicationRecord
- enum locale: Locale.code_name_to_db_encoding
-
- # Associations
- has_many :ta_mappings, dependent: :destroy
- has_many :tas, through: :ta_mappings
- has_many :assignments, dependent: :destroy
belongs_to :instructor, class_name: 'User', foreign_key: 'instructor_id'
- belongs_to :institution, foreign_key: 'institutions_id'
- has_many :participants, class_name: 'CourseParticipant', foreign_key: 'parent_id', dependent: :destroy
- has_many :course_teams, foreign_key: 'parent_id', dependent: :destroy
- has_one :course_node, foreign_key: 'node_object_id', dependent: :destroy
- has_many :notifications, dependent: :destroy
- has_paper_trail
-
- # Validations
+ belongs_to :institution, foreign_key: 'institution_id'
validates :name, presence: true
validates :directory_path, presence: true
+ has_many :ta_mappings, dependent: :destroy
+ has_many :tas, through: :ta_mappings
- # Return teams associated with this course
- def get_teams
- course_teams
- end
-
- # Get all participants in this course
- def get_participants
- participants
- end
-
- # Get a specific participant by user ID
- def get_participant(user_id)
- participants.find_by(user_id: user_id)
- end
-
- # Check if a user is on any team in the course
- def user_on_team?(user)
- course_teams.joins(:users).exists?(users: { id: user.id })
+ # Returns the submission directory for the course
+ def path
+ raise 'Path can not be created as the course must be associated with an instructor.' if instructor_id.nil?
+ Rails.root + '/' + Institution.find(institution_id).name.gsub(" ", "") + '/' + User.find(instructor_id).name.gsub(" ", "") + '/' + directory_path + '/'
end
-
- # Add a user as a participant to this course
- def add_participant(user_name)
- user = User.find_by(name: user_name)
- raise "No user account exists with the name #{user_name}. Please create the user first." unless user
-
- participant = participants.find_by(user_id: user.id)
- if participant
- raise "The user #{user.name} is already a participant."
+
+ # Add a Teaching Assistant to the course
+ def add_ta(user)
+ if user.nil?
+ return { success: false, message: "The user with id #{user.id} does not exist" }
+ elsif TaMapping.exists?(ta_id: user.id, course_id: id)
+ return { success: false, message: "The user with id #{user.id} is already a TA for this course." }
else
- participants.create(user_id: user.id, permission_granted: user.master_permission_granted)
- end
- end
-
- def remove_participants(user_ids)
- user_ids.each do |user_id|
- participant = participants.find_by(user_id: user_id)
- raise "User with ID #{user_id} is not a participant." if participant.nil?
-
- participant.destroy
+ ta_mapping = TaMapping.create(ta_id: user.id, course_id: id)
+ user.update(role: Role::TEACHING_ASSISTANT)
+ if ta_mapping.save
+ return { success: true, data: ta_mapping.slice(:course_id, :ta_id) }
+ else
+ return { success: false, message: ta_mapping.errors }
+ end
end
end
- # Add a user to a team
- def add_user_to_team(user, team_id)
- team = course_teams.find_by(id: team_id)
- raise "Team not found in this course." unless team
-
- if user_on_team?(user)
- raise "The user #{user.name} is already assigned to a team for this course."
+ # Removes Teaching Assistant from the course
+ def remove_ta(ta_id)
+ ta_mapping = ta_mappings.find_by(ta_id: ta_id, course_id: :id)
+ return { success: false, message: "No TA mapping found for the specified course and TA" } if ta_mapping.nil?
+ ta = User.find(ta_mapping.ta_id)
+ ta_count = TaMapping.where(ta_id: ta_id).size - 1
+ if ta_count.zero?
+ ta.update(role: Role::STUDENT)
end
-
- team.add_member(user, id)
+ ta_mapping.destroy
+ { success: true, ta_name: ta.name }
end
- # Copy participants from an assignment to this course
- def copy_participants_from_assignment(assignment_id)
- participants = AssignmentParticipant.where(parent_id: assignment_id)
- errors = []
-
- participants.each do |participant|
- user = User.find(participant.user_id)
- begin
- add_participant(user.name)
- rescue StandardError => e
- errors << e.message
- end
- end
-
- raise errors.join(' ') unless errors.empty?
+ # Creates a copy of the course
+ def copy_course
+ new_course = dup
+ new_course.directory_path += '_copy'
+ new_course.name += '_copy'
+ new_course.save
end
-
- # Returns the path for this course
- def path
- raise 'Path cannot be created. The course must be associated with an instructor.' if instructor_id.nil?
-
- Rails.root.join('pg_data', FileHelper.clean_path(instructor.name), FileHelper.clean_path(directory_path))
+ #E2479
+ # Checks if a user is eligible to join a specific team for a course.
+# This method ensures that:
+# - The user is not already a member of another team for the course.
+# - The user is a valid participant in the course.
+# Params:
+# - user: The user to be validated for team membership.
+# Returns:
+# - A hash indicating success or failure:
+# - { success: true } if the user can be added to the team.
+# - { success: false, error: "Reason for failure" } if the user cannot be added.
+def valid_team_participant?(user)
+ # Check if the user is already a member of another team for the same course.
+ if already_on_team?(user)
+ { success: false, error: "This user is already assigned to a team for this course" }
+
+ # Check if the user is a participant in the course associated with this team.
+ elsif CourseParticipant.find_by(user_id: user.id, parent_id: course_id).nil?
+ { success: false, error: "#{user.name} is not a participant in this course" }
+
+ # If both checks pass, the user is eligible to join the team.
+ else
+ { success: true }
end
-
- # Analytics
- require 'analytic/course_analytic'
- include CourseAnalytic
end
+
+end
\ No newline at end of file
diff --git a/app/models/mentor_management.rb b/app/models/mentor_management.rb
new file mode 100644
index 000000000..54a710964
--- /dev/null
+++ b/app/models/mentor_management.rb
@@ -0,0 +1,122 @@
+class MentorManagement
+ # Select a mentor using the following algorithm
+ #
+ # 1) Find all assignment participants for the
+ # assignment with id [assignment_id] whose
+ # duty is the same as [Particpant#DUTY_MENTOR].
+ # 2) Count the number of teams those participants
+ # are a part of, acting as a proxy for the
+ # number of teams they mentor.
+ # 3) Return the mentor with the fewest number of
+ # teams they're currently mentoring.
+ #
+ # This method's runtime is O(n lg n) due to the call to
+ # Hash#sort_by. This assertion assumes that the
+ # database management system is capable of fetching the
+ # required rows at least as quickly.
+ #
+ # Implementation detail: Any tie between the top 2
+ # mentors is decided by the Hash#sort_by algorithm.
+ #
+ # @return The id of the mentor with the fewest teams
+ # they are assigned to. Returns `nil` if there are
+ # no participants with mentor duty for [assignment_id].
+ def self.select_mentor(assignment_id)
+ mentor_user_id, = zip_mentors_with_team_count(assignment_id).first
+ User.where(id: mentor_user_id).first
+ end
+
+ # = Mentor Management
+ # E2115: Handles calls when an assignment has the auto_assign_mentor flag enabled and triggered by the event when a new member joins an assignment team.
+ #
+ # This event happens when:
+ # 1.) An invited student user accepts and successfully added to a team from
+ # app/models/invitation.rb
+ # 2.) A student user is successfully added to the team manually from
+ # app/controllers/teams_users_controller.rb.
+ #
+ # This method will determine if a mentor needs to be assigned, if so,
+ # selects one, and adds the mentor to the team if:
+ # 1.) The assignment does not have a topic.
+ # 2.) If the team has reached >50% full capacity.
+ # 3.) If the team does not have a mentor.
+ def self.assign_mentor(assignment_id, team_id)
+ assignment = Assignment.find(assignment_id)
+ team = Team.find(team_id)
+
+ # RuboCop 'use guard clause instead of nested conditionals'
+ # return if assignments can't accept mentors
+ return unless assignment.auto_assign_mentor
+
+ # RuboCop 'use guard clause instead of nested conditionals'
+ # return if the assignment or team already have a topic
+ return if assignment.topics? || !team.topic.nil?
+
+ curr_team_size = Team.size(team_id)
+ max_team_members = Assignment.find(assignment_id).max_team_size
+
+ # RuboCop 'use guard clause instead of nested conditionals'
+ # return if the team size hasn't reached > 50% of capacity
+ return if curr_team_size * 2 <= max_team_members
+
+ # RuboCop 'use guard clause instead of nested conditionals'
+ # return if there's already a mentor in place
+ return if team.participants.any? { |participant| participant.can_mentor == true }
+
+ mentor_user = select_mentor(assignment_id)
+
+ # Add the mentor using team model class.
+ team_member_added = mentor_user.nil? ? false : team.add_member(mentor_user, assignment_id)
+
+ return unless team_member_added
+
+ notify_team_of_mentor_assignment(mentor_user, team)
+ end
+
+ def self.notify_team_of_mentor_assignment(mentor, team)
+ members = team.users
+ emails = members.map(&:email)
+ members_info = members.map { |mem| "#{mem.fullname} - #{mem.email}" }
+ mentor_info = "#{mentor.fullname} (#{mentor.email})"
+ message = "#{mentor_info} has been assigned as your mentor for assignment #{Assignment.find(team.parent_id).name} Current members: #{members_info.join(' ')}"
+
+ Mailer.delayed_message(bcc: emails,
+ subject: '[Expertiza]: New Mentor Assignment',
+ body: message).deliver_now
+ end
+
+ # Returns true if [user] is a mentor, and false if not.
+ #
+ # [user] must be a User object.
+ #
+ # Checks the Participant relation to see if a row exists with
+ # user_id == user.id that also has 'mentor' in the duty attribute.
+ def self.user_a_mentor?(user)
+ Participant.exists?(user_id: user.id, can_mentor: true)
+ end
+
+ # Select all the participants who's duty in the participant
+ # table is [DUTY_MENTOR], and who are a participant of
+ # [assignment_id].
+ #
+ # @see participant.rb for the value of DUTY_MENTOR
+ def self.mentors_for_assignment(assignment_id)
+ Participant.where(parent_id: assignment_id, can_mentor: true)
+ end
+
+ # Produces a hash mapping mentor's user_ids to the aggregated
+ # number of teams they're part of, which acts as a proxy for
+ # the number of teams they're mentoring.
+ def self.zip_mentors_with_team_count(assignment_id)
+ mentor_ids = mentors_for_assignment(assignment_id).pluck(:user_id)
+
+ return [] if mentor_ids.empty?
+
+ team_counts = {}
+ mentor_ids.each { |id| team_counts[id] = 0 }
+ #E2351 removed (:team_id) after .count to fix balancing algorithm
+ team_counts.update(TeamsUser.where(user_id: mentor_ids).group(:user_id).count)
+
+ team_counts.sort_by { |_, v| v }
+ end
+end
diff --git a/app/models/mentored_team.rb b/app/models/mentored_team.rb
new file mode 100644
index 000000000..6b38ac48e
--- /dev/null
+++ b/app/models/mentored_team.rb
@@ -0,0 +1,27 @@
+class MentoredTeam < AssignmentTeam
+ # Class created during refactoring of E2351
+ # Overridden method to include the MentorManagement workflow
+ def add_member(user, _assignment_id = nil)
+ raise "The user #{user.name} is already a member of the team #{name}" if user?(user)
+ raise "A mentor already exists for team #{name}" if mentor_exists? && user.mentor_role?
+ can_add_member = false
+ unless full? || user.mentor_role?
+ can_add_member = true
+ t_user = TeamsUser.create(user_id: user.id, team_id: id)
+ parent = TeamNode.find_by(node_object_id: id)
+ TeamUserNode.create(parent_id: parent.id, node_object_id: t_user.id)
+ add_participant(parent_id, user)
+ ExpertizaLogger.info LoggerMessage.new('Model:Team', user.name, "Added member to the team #{id}")
+ end
+ if can_add_member
+ MentorManagement.assign_mentor(_assignment_id, id) if user.mentor_role?
+ end
+ can_add_member
+ end
+ #E2479
+ private
+
+ def mentor_exists?
+ teams_users.where(role: 'mentor').exists?
+ end
+end
diff --git a/app/models/team.rb b/app/models/team.rb
index 94cc9a90a..1a7c2973f 100644
--- a/app/models/team.rb
+++ b/app/models/team.rb
@@ -1,324 +1,147 @@
class Team < ApplicationRecord
+ has_many :signed_up_teams, dependent: :destroy
has_many :teams_users, dependent: :destroy
- has_many :users, through: :teams_users
has_many :join_team_requests, dependent: :destroy
has_one :team_node, foreign_key: :node_object_id, dependent: :destroy
- has_many :signed_up_teams, dependent: :destroy
+ has_many :users, through: :teams_users
has_many :bids, dependent: :destroy
- has_paper_trail
-
+ has_many :participants
+ belongs_to :assignment
+ attr_accessor :max_participants
scope :find_team_for_assignment_and_user, lambda { |assignment_id, user_id|
joins(:teams_users).where('teams.parent_id = ? AND teams_users.user_id = ?', assignment_id, user_id)
}
-
- # Allowed types of teams -- ASSIGNMENT teams or COURSE teams
- def self.allowed_types
- # non-interpolated array of single-quoted strings
- %w[Assignment Course]
- end
-
- # Get the participants of the given team
- def participants
- users.where(parent_id: parent_id || current_user_id).flat_map(&:participants)
- end
- alias get_participants participants
-
- # copies content of one object to the another
- def self.copy_content(source, destination)
- source.each do |each_element|
- each_element.copy(destination.id)
- end
- end
-
- # enum method for team clone operations
- def self.team_operation
- { inherit: 'inherit', bequeath: 'bequeath' }.freeze
- end
-
- # Get the response review map
- def responses
- participants.flat_map(&:responses)
- end
-
- # Delete the given team
- def delete
- TeamsUser.where(team_id: id).find_each(&:destroy)
- node = TeamNode.find_by(node_object_id: id)
- node.destroy if node
- destroy
- end
-
- # Get the node type of the tree structure
- def node_type
- 'TeamNode'
- end
-
- # Get the names of the users
- def author_names
- names = []
- users.each do |user|
- names << user.fullname
- end
- names
- end
-
- # Check if the user exist
- def user?(user)
- users.include? user
- end
-
- # Check if the current team is full?
+ # TODO Team implementing Teams controller and model should implement this method better.
+ # TODO partial implementation here just for the functionality needed for join_team_tequests controller
def full?
- return false if parent_id.nil? # course team, does not max_team_size
-
- max_team_members = Assignment.find(parent_id).max_team_size
- curr_team_size = Team.size(id)
- curr_team_size >= max_team_members
- end
-
- # Add member to the team, changed to hash by E1776
- def add_member(user, _assignment_id = nil)
- raise "The user #{user.name} is already a member of the team #{name}" if user?(user)
-
- can_add_member = false
- unless full?
- can_add_member = true
- t_user = TeamsUser.create(user_id: user.id, team_id: id)
- parent = TeamNode.find_by(node_object_id: id)
- TeamUserNode.create(parent_id: parent.id, node_object_id: t_user.id)
- add_participant(parent_id, user)
- ExpertizaLogger.info LoggerMessage.new('Model:Team', user.name, "Added member to the team #{id}")
- end
- can_add_member
- end
-
- # Define the size of the team
- def self.size(team_id)
- #TeamsUser.where(team_id: team_id).count
- count = 0
- members = TeamsUser.where(team_id: team_id)
- members.each do |member|
- member_name = member.name
- unless member_name.include?(' (Mentor)')
- count = count + 1
- end
- end
- count
- end
-
- # Copy method to copy this team
- def copy_members(new_team)
- members = TeamsUser.where(team_id: id)
- members.each do |member|
- t_user = TeamsUser.create(team_id: new_team.id, user_id: member.user_id)
- parent = Object.const_get(parent_model).find(parent_id)
- TeamUserNode.create(parent_id: parent.id, node_object_id: t_user.id)
- end
- end
-
- # Check if the team exists
- def self.check_for_existing(parent, name, team_type)
- list = Object.const_get(team_type + 'Team').where(parent_id: parent.id, name: name)
- raise TeamExistsError, "The team name #{name} is already in use." unless list.empty?
- end
-
- # Algorithm
- # Start by adding single members to teams that are one member too small.
- # Add two-member teams to teams that two members too small. etc.
- def self.randomize_all_by_parent(parent, team_type, min_team_size)
- participants = Participant.where(parent_id: parent.id, type: parent.class.to_s + 'Participant', can_mentor: [false, nil])
- participants = participants.sort { rand(-1..1) }
- users = participants.map { |p| User.find(p.user_id) }.to_a
- # find teams still need team members and users who are not in any team
- teams = Team.where(parent_id: parent.id, type: parent.class.to_s + 'Team').to_a
- teams.each do |team|
- TeamsUser.where(team_id: team.id).each do |teams_user|
- users.delete(User.find(teams_user.user_id))
- end
- end
- teams.reject! { |team| Team.size(team.id) >= min_team_size }
- # sort teams that still need members by decreasing team size
- teams.sort_by { |team| Team.size(team.id) }.reverse!
- # insert users who are not in any team to teams still need team members
- assign_single_users_to_teams(min_team_size, parent, teams, users) if !users.empty? && !teams.empty?
- # If all the existing teams are fill to the min_team_size and we still have more users, create teams for them.
- create_team_from_single_users(min_team_size, parent, team_type, users) unless users.empty?
- end
-
- # Creates teams from a list of users based on minimum team size
- # Then assigns the created team to the parent object
- def self.create_team_from_single_users(min_team_size, parent, team_type, users)
- num_of_teams = users.length.fdiv(min_team_size).ceil
- next_team_member_index = 0
- (1..num_of_teams).to_a.each do |i|
- team = Object.const_get(team_type + 'Team').create(name: 'Team_' + i.to_s, parent_id: parent.id)
- TeamNode.create(parent_id: parent.id, node_object_id: team.id)
- min_team_size.times do
- break if next_team_member_index >= users.length
-
- user = users[next_team_member_index]
- team.add_member(user, parent.id)
- next_team_member_index += 1
- end
- end
- end
-
- # Assigns list of users to list of teams based on minimum team size
- def self.assign_single_users_to_teams(min_team_size, parent, teams, users)
- teams.each do |team|
- curr_team_size = Team.size(team.id)
- member_num_difference = min_team_size - curr_team_size
- while member_num_difference > 0
- team.add_member(users.first, parent.id)
- users.delete(users.first)
- member_num_difference -= 1
- break if users.empty?
- end
- break if users.empty?
- end
- end
-
- # Generate the team name
- def self.generate_team_name(_team_name_prefix = '')
- last_team = Team.where('name LIKE ?', "#{_team_name_prefix} Team_%")
- .order("CAST(SUBSTRING(name, LENGTH('#{_team_name_prefix} Team_') + 1) AS UNSIGNED) DESC")
- .first
- counter = last_team ? last_team.name.scan(/\d+/).first.to_i + 1 : 1
- team_name = "#{_team_name_prefix} Team_#{counter}"
- team_name
- end
-
- # Extract team members from the csv and push to DB, changed to hash by E1776
- def import_team_members(row_hash)
- row_hash[:teammembers].each_with_index do |teammate, _index|
- user = User.find_by(name: teammate.to_s)
- if user.nil?
- raise ImportError, "The user '#{teammate}' was not found. Create this user?"
- else
- add_member(user) if TeamsUser.find_by(team_id: id, user_id: user.id).nil?
- end
- end
- end
-
- # changed to hash by E1776
- def self.import(row_hash, id, options, teamtype)
- raise ArgumentError, 'Not enough fields on this line.' if row_hash.empty? || (row_hash[:teammembers].empty? && (options[:has_teamname] == 'true_first' || options[:has_teamname] == 'true_last')) || (row_hash[:teammembers].empty? && (options[:has_teamname] == 'true_first' || options[:has_teamname] == 'true_last'))
-
- if options[:has_teamname] == 'true_first' || options[:has_teamname] == 'true_last'
- name = row_hash[:teamname].to_s
- team = where(['name =? && parent_id =?', name, id]).first
- team_exists = !team.nil?
- name = handle_duplicate(team, name, id, options[:handle_dups], teamtype)
+ max_participants ||= 3
+ if participants.count >= max_participants
+ true
else
- if teamtype.is_a?(CourseTeam)
- name = generate_team_name(Course.find(id).name)
- elsif teamtype.is_a?(AssignmentTeam)
- name = generate_team_name(Assignment.find(id).name)
- end
- end
- if name
- team = Object.const_get(teamtype.to_s).create_team_and_node(id)
- team.name = name
- team.save
- end
-
- # insert team members into team unless team was pre-existing & we ignore duplicate teams
+ false
+ end
+ end
+
+ # Adds a user to the team while handling potential errors such as duplicate membership.
+# This is a wrapper around the `add_member` method with error handling.
+# Params:
+# - user: The user to be added to the team.
+# - parent_id: The ID of the parent entity (assignment or course).
+# Returns:
+# - true if the user was successfully added.
+# - A hash with an error message if the addition fails.
+def add_member_with_handling(user, parent_id)
+ begin
+ # Attempt to add the user to the team.
+ addition_result = add_member(user, parent_id)
+ addition_result
+ rescue StandardError => e
+ # Return a failure message if an error occurs (e.g., user already in the team).
+ { success: false, error: "The user #{user.name} is already a member of the team #{name}" }
+ end
+end
+
+# Adds a user to the team if they are not already a member and the team is not full.
+# Also creates the necessary team user and team node entries.
+# Params:
+# - user: The user to be added.
+# - _assignment_id (optional): The ID of the assignment (not used directly in this implementation).
+# Returns:
+# - true if the user was successfully added to the team.
+# - false if the team is full or if the user cannot be added.
+def add_member(user, _assignment_id = nil)
+ # Raise an error if the user is already a member of the team.
+ raise "The user #{user.name} is already a member of the team #{name}" if member?(user)
+
+ # Check if the team is not full before adding the user.
+ if !full?
+ # Create a relationship between the user and the team.
+ team_user_relationship = TeamsUser.create(user_id: user.id, team_id: id)
+
+ # Create the corresponding team user node in the hierarchy.
+ team_node = TeamNode.find_by(node_object_id: id)
+ TeamUserNode.create(parent_id: team_node.id, node_object_id: team_user_relationship.id)
+
+ # Add the user as a participant for the parent assignment or course.
+ add_participant_to_parent_entity(parent_id, user)
+
+ # Log the successful addition of the user to the team.
+ ExpertizaLogger.info LoggerMessage.new('Model:Team', user.name, "Added member to the team #{id}")
+ true
+ else
+ false
+ end
+end
+
+# Checks if the given user is already a member of the team.
+# Params:
+# - user: The user to check for membership.
+# Returns:
+# - true if the user is already a member of the team.
+# - false otherwise.
+def participant_present?(user)
+ users.include?(user)
+end
+
+# Retrieves all participants associated with the team.
+# Participants are derived from the users belonging to the team.
+# Returns:
+# - An array of participant objects.
+def participants
+ users.where(parent_id: parent_id || current_user_id).flat_map(&:participants)
+end
+alias get_participants participants
+
+# Retrieves the full names of all users in the team.
+# Returns:
+# - An array of strings representing the full names of the team members.
+def participant_full_names
+ users.map(&:fullname)
+end
+alias author_names participant_full_names
+
+# Determines the current size of the team, excluding mentors.
+# Params:
+# - team_id: The ID of the team whose size is being checked.
+# Returns:
+# - The number of non-mentor members in the team.
+def self.size(team_id)
+ count = 0
+ team_participants = TeamsUser.where(team_id: team_id)
+
+ # Exclude mentors from the count.
+ team_participants.each do |team_participants|
+ team_participants_name = team_participants.name
+ count += 1 unless team_participants_name.include?(' (Mentor)')
+ end
+ count
+end
+
+# Checks if a team with the given name already exists for a parent entity (assignment or course).
+# Raises an error if a team with the same name already exists.
+# Params:
+# - parent: The parent entity (assignment or course) associated with the team.
+# - name: The name of the team to check for uniqueness.
+# - team_type: The type of team (e.g., 'Assignment', 'Course').
+def self.check_for_existing(parent, name, team_type)
+ existing_teams = Object.const_get("#{team_type}Team").where(parent_id: parent.id, name: name)
+
+ # Raise an error if any team with the same name is found.
+ unless existing_teams.empty?
+ raise TeamExistsError, "The team name #{name} is already in use."
+ end
+end
+
+# Retrieves all teams that a specific user belongs to for a given assignment.
+# Params:
+# - assignment_id: The ID of the assignment.
+# - user_id: The ID of the user.
+# Returns:
+# - A list of team IDs for the user within the specified assignment.
+def self.find_team_participants(assignment_id, user_id)
+ TeamsUser.joins('INNER JOIN teams ON teams_users.team_id = teams.id')
+ .select('teams.id as team_id')
+ .where('teams.parent_id = ? AND teams_users.user_id = ?', assignment_id, user_id)
+end
- team.import_team_members(row_hash) unless team_exists && options[:handle_dups] == 'ignore'
- end
-
- # Handle existence of the duplicate team
- def self.handle_duplicate(team, name, id, handle_dups, teamtype)
- return name if team.nil? # no duplicate
- return nil if handle_dups == 'ignore' # ignore: do not create the new team
-
- if handle_dups == 'rename' # rename: rename new team
- if teamtype.is_a?(CourseTeam)
- return generate_team_name(Course.find(id).name)
- elsif teamtype.is_a?(AssignmentTeam)
- return generate_team_name(Assignment.find(id).name)
- end
- end
- if handle_dups == 'replace' # replace: delete old team
- team.delete
- return name
- else # handle_dups = "insert"
- return nil
- end
- end
-
- # Export the teams to csv
- def self.export(csv, parent_id, options, teamtype)
- if teamtype.is_a?(CourseTeam)
- teams = CourseTeam.where(parent_id: parent_id)
- elsif teamtype.is_a?(AssignmentTeam)
- teams = AssignmentTeam.where(parent_id: parent_id)
- end
- teams.each do |team|
- output = []
- output.push(team.name)
- if options[:team_name] == 'false'
- team_members = TeamsUser.where(team_id: team.id)
- team_members.each do |user|
- output.push(user.name)
- end
- end
- csv << output
- end
- csv
- end
-
- # Create the team with corresponding tree node
- def self.create_team_and_node(id)
- parent = parent_model id # current_task will be either a course object or an assignment object.
- team_name = Team.generate_team_name(parent.name)
- team = create(name: team_name, parent_id: id)
- # new teamnode will have current_task.id as parent_id and team_id as node_object_id.
- TeamNode.create(parent_id: id, node_object_id: team.id)
- ExpertizaLogger.info LoggerMessage.new('Model:Team', '', "New TeamNode created with teamname #{team_name}")
- team
- end
-
- # E1991 : This method allows us to generate
- # team names based on whether anonymized view
- # is set or not. The logic is similar to
- # existing logic of User model.
- def name(ip_address = nil)
- if User.anonymized_view?(ip_address)
- return "Anonymized_Team_#{self[:id]}"
- else
- return self[:name]
- end
- end
-
- # REFACTOR END:: class methods import export moved from course_team & assignment_team to here
-
- # Create the team with corresponding tree node and given users
- def self.create_team_with_users(parent_id, user_ids)
- team = create_team_and_node(parent_id)
-
- user_ids.each do |user_id|
- remove_user_from_previous_team(parent_id, user_id)
-
- # Create new team_user and team_user node
- team.add_member(User.find(user_id))
- end
- team
- end
-
- # Removes the specified user from any team of the specified assignment
- def self.remove_user_from_previous_team(parent_id, user_id)
- team_user = TeamsUser.where(user_id: user_id).find { |team_user_obj| team_user_obj.team.parent_id == parent_id }
- begin
- team_user.destroy
- rescue StandardError
- nil
- end
- end
-
- def self.find_team_users(assignment_id, user_id)
- TeamsUser.joins('INNER JOIN teams ON teams_users.team_id = teams.id')
- .select('teams.id as t_id')
- .where('teams.parent_id = ? and teams_users.user_id = ?', assignment_id, user_id)
- end
end
\ No newline at end of file
diff --git a/app/models/teams_participants.rb b/app/models/teams_participants.rb
index d2086e969..a12c7608e 100644
--- a/app/models/teams_participants.rb
+++ b/app/models/teams_participants.rb
@@ -3,74 +3,70 @@ class TeamsUser < ApplicationRecord
belongs_to :team
has_one :team_user_node, foreign_key: 'node_object_id', dependent: :destroy
has_paper_trail
- # attr_accessible :user_id, :team_id # unnecessary protected attributes
- def name(ip_address = nil)
- name = user.name(ip_address)
-
- # E2115 Mentor Management
- # Indicate that someone is a Mentor in the UI. The view code is
- # often hard to follow, and this is the best place we could find
- # for this to go.
- name += ' (Mentor)' if MentorManagement.user_a_mentor?(user)
- name
- end
+ def get_team_members(team_id)
+ team_members = TeamsUser.where('team_id = ?', team_id)
+ user_ids = team_members.pluck(:user_id)
+ users = User.where(id: user_ids)
- def delete
- TeamUserNode.find_by(node_object_id: id).destroy
- team = self.team
- destroy
- team.delete if team.teams_users.empty?
+ return users
end
- def get_team_members(team_id); end
-
# Removes entry in the TeamUsers table for the given user and given team id
def self.remove_team(user_id, team_id)
team_user = TeamsUser.where('user_id = ? and team_id = ?', user_id, team_id).first
team_user&.destroy
end
- # Returns the first entry in the TeamUsers table for a given team id
- def self.first_by_team_id(team_id)
- TeamsUser.where('team_id = ?', team_id).first
- end
+#E2479
+# Retrieves the name of the user associated with the team member.
+# Optionally appends ' (Mentor)' if the user is a mentor.
+# Params:
+# - ip_address (optional): The IP address of the user (used for logging or contextual name resolution).
+# Returns:
+# - The name of the user, with '(Mentor)' appended if the user is a mentor.
+def display_name(ip_address = nil)
+ participant_name = user.name(ip_address)
+ participant_name += ' (Mentor)' if MentorManagement.user_a_mentor?(user)
+ participant_name
+end
- # Determines whether a team is empty of not
- def self.team_empty?(team_id)
- team_members = TeamsUser.where('team_id = ?', team_id)
- team_members.blank?
- end
+# Deletes multiple team members (identified by their IDs) in bulk.
+# This method is used for efficient removal of multiple `TeamsUser` records.
+# Params:
+# - team_user_ids: An array of IDs of the `TeamsUser` records to be deleted.
+# Returns:
+# - The number of records deleted (implicit return from `destroy_all`).
+def self.bulk_delete(team_user_ids)
+ # Delete all `TeamsUser` records matching the provided IDs.
+ where(id: team_user_ids).destroy_all
+end
+
+# Checks whether a specific user is a member of a given team.
+# Params:
+# - user_id: The ID of the user to check.
+# - team_id: The ID of the team to check for membership.
+# Returns:
+# - true if the user is a member of the team.
+# - false otherwise.
+def self.participant_part_of_team?(user_id, team_id)
+ # Check if a `TeamsUser` record exists with the specified user and team IDs.
+ exists?(user_id: user_id, team_id: team_id)
+end
+
+# Checks whether a team is empty (i.e., has no members).
+# Params:
+# - team_id: The ID of the team to check.
+# Returns:
+# - true if the team has no members.
+# - false otherwise.
+def self.is_team_empty?(team_id)
+ # Retrieve all members of the team.
+ team_members = TeamsUser.where('team_id = ?', team_id)
+
+ # Return true if the team has no members; false otherwise.
+ team_members.blank?
+end
- # Add member to the team they were invited to and accepted the invite for
- def self.add_member_to_invited_team(invitee_user_id, invited_user_id, assignment_id)
- can_add_member = false
- users_teams = TeamsUser.where(['user_id = ?', invitee_user_id])
- users_teams.each do |team|
- new_team = AssignmentTeam.where(['id = ? and parent_id = ?', team.team_id, assignment_id]).first
- unless new_team.nil?
- can_add_member = new_team.add_member(User.find(invited_user_id), assignment_id)
- end
- end
- can_add_member
- end
- # 2015-5-27 [zhewei]:
- # We just remove the topic_id field from the participants table.
- def self.team_id(assignment_id, user_id)
- # team_id variable represents the team_id for this user in this assignment
- team_id = nil
- teams_users = TeamsUser.where(user_id: user_id)
- teams_users.each do |teams_user|
- if teams_user.team_id == nil
- next
- end
- team = Team.find(teams_user.team_id)
- if team.parent_id == assignment_id
- team_id = teams_user.team_id
- break
- end
- end
- team_id
- end
end
\ No newline at end of file
From 52fdca59f44e19437ecabb15d49cfa5fc87d2081 Mon Sep 17 00:00:00 2001
From: manideepika21
Date: Tue, 3 Dec 2024 23:06:09 -0500
Subject: [PATCH 14/18] Changed the variable names and added comments
---
Gemfile | 2 +-
.../v1}/teams_participants_controller.rb | 71 ++++++-----
app/models/assignment.rb | 26 +---
app/models/team.rb | 43 +++++--
app/models/teams_participants.rb | 114 +++---------------
.../teams_participants_controller_spec.rb | 89 ++++++++++++++
spec/factories/factories.rb | 22 +++-
7 files changed, 202 insertions(+), 165 deletions(-)
rename app/controllers/{ => api/v1}/teams_participants_controller.rb (60%)
create mode 100644 spec/controllers/teams_participants_controller_spec.rb
diff --git a/Gemfile b/Gemfile
index 1786836d0..53202af81 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,7 +1,7 @@
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
-ruby '3.2.1'
+ruby '3.2.5'
gem 'mysql2', '~> 0.5.5'
gem 'puma', '~> 5.0'
diff --git a/app/controllers/teams_participants_controller.rb b/app/controllers/api/v1/teams_participants_controller.rb
similarity index 60%
rename from app/controllers/teams_participants_controller.rb
rename to app/controllers/api/v1/teams_participants_controller.rb
index 1606f92a7..c500ee01a 100644
--- a/app/controllers/teams_participants_controller.rb
+++ b/app/controllers/api/v1/teams_participants_controller.rb
@@ -1,4 +1,4 @@
-class TeamsParticipantsController < ApplicationController
+class Api::V1::TeamsParticipantsController < ApplicationController
include AuthorizationHelper
# Determines if the current user is allowed to perform the requested action.
@@ -11,7 +11,7 @@ def action_allowed?
end
# Fetches and renders an auto-complete list of possible team members based on a partial name input.
- def auto_complete_for_participtant_name
+ def auto_complete_for_participant_name
# Fetch the current team using the session-stored `team_id`.
current_team = Team.find(session[:team_id])
@@ -25,7 +25,7 @@ def auto_complete_for_participtant_name
# Updates the duty (role) assigned to a participant in a team.
def update_duties
# Find the team member relationship using the provided ID.
- team_member_relationship = TeamsParticipant.find(params[:teams_user_id])
+ team_member_relationship = TeamsUser.find(params[:teams_user_id])
# Update the duty of the team member.
team_member_relationship.update_attribute(:duty_id, params[:teams_user]['duty_id'])
@@ -35,7 +35,7 @@ def update_duties
end
# Displays a paginated list of all participants in a specific team.
- def list
+ def list_participants
# Fetch the team based on the provided ID.
current_team = Team.find(params[:id])
@@ -43,46 +43,58 @@ def list
associated_assignment_or_course = Assignment.find(current_team.parent_id)
# Query and paginate participants of the current team.
- @team_participants = TeamsParticipant.page(params[:page]).per_page(10).where(team_id: current_team.id)
+ @team_participants = TeamsUser.page(params[:page]).per_page(10).where(team_id: current_team.id)
@team = current_team
@assignment = associated_assignment_or_course
end
# Renders the form for adding a new participant to a team.
- def new
+ def add_new_participant
# Fetch the team for which a participant is to be added.
@team = Team.find(params[:id])
end
# Adds a new participant to a team after validation.
- def create
+ def create_participant
# Find the user by their name from the input.
- participant = find_user_by_name
+ find_participant = find_participant_by_name
# Fetch the team using the provided ID.
current_team = find_team_by_id
- # Return early if validation fails.
- return unless validate_participant_and_team(participant, current_team)
-
- # Add the participant to the team.
- add_participant_to_team(participant, current_team)
+ if validate_participant_and_team(participant, team)
+ if team.add_participants_with_handling(participant, team.parent_id)
+ undo_link("The participant \"#{participant.name}\" has been successfully added to \"#{team.name}\".")
+ else
+ flash[:error] = 'This team already has the maximum number of members.'
+ end
+ end
# Redirect to the list of teams for the parent assignment or course.
redirect_to controller: 'teams', action: 'list', id: current_team.parent_id
end
+ #Deletes the selected participant
+ def delete_selected_participant
+ @teams_user = TeamsUser.find(params[:id])
+ parent_id = Team.find(@teams_user.team_id).parent_id
+ @user = User.find(@teams_user.user_id)
+ @teams_user.destroy
+ undo_link("The team user \"#{@user.name}\" has been successfully removed. ")
+ redirect_to controller: 'teams', action: 'list', id: parent_id
+ end
+
private
# Helper method to find a user by their name.
- def find_user_by_name
+ def find_participant_by_name
# Locate the user by their name.
- participant = User.find_by(name: params[:user][:name].strip)
+ find_participant = User.find_by(name: params[:user][:name].strip)
# Display an error if the user is not found.
- unless participant
- flash[:error] = user_not_found_error
+ unless find_participant
+ flash[:error] = participant_not_found_error
redirect_back fallback_location: root_path
end
participant
@@ -97,9 +109,9 @@ def find_team_by_id
def validate_participant_and_team(participant, team)
# Check if the participant is valid for the team type.
validation_result = if team.is_a?(AssignmentTeam)
- Assignment.find(team.parent_id).valid_team_member?(participant)
+ Assignment.find(team.parent_id).valid_team_participant?(participant)
else
- Course.find(team.parent_id).valid_team_member?(participant)
+ Course.find(team.parent_id).valid_team_participant?(participant)
end
# Handle validation errors if any.
@@ -113,24 +125,29 @@ def validate_participant_and_team(participant, team)
end
# Adds the participant to the team while handling constraints.
- def add_participant_to_team(participant, team)
+ def add_participant_to_team(find_participant, team)
# Add the participant to the team and handle the outcome.
- addition_result = team.add_member_with_handling(participant, team.parent_id)
- handle_addition_result(participant, team, addition_result)
+ addition_result = find_team_by_id.add_participant(find_participant, team.parent_id)
+ handle_addition_result(find_participant, team, addition_result)
end
# Handles the result of adding a participant to the team.
- def handle_addition_result(participant, team, addition_result)
+ def handle_addition_result(find_participant, team, addition_result)
if addition_result == false
flash[:error] = 'This team already has the maximum number of members.'
else
- undo_link("The participant \"#{participant.name}\" has been successfully added to \"#{team.name}\".")
+ undo_link("The team user \"#{find_participant.name}\" has been successfully added to \"#{team.name}\".")
end
end
# Generates an error message when a user is not found.
- def user_not_found_error
- new_user_url = url_for controller: 'users', action: 'new'
- "\"#{params[:user][:name].strip}\" is not defined. Please create this user before continuing."
+ def participant_not_found_error
+ new_participnat_url = url_for controller: 'users', action: 'new'
+ "\"#{params[:user][:name].strip}\" is not defined. Please create this user before continuing."
+ end
+
+ def non_participant_error(find_participant, parent_id, model)
+ urlParticipantList = url_for controller: 'participants', action: 'list', id: parent_id, model: model, authorization: 'participant'
+ "\"#{find_participant.name}\" is not a participant of the current course/assignment. Please add this user before continuing."
end
end
diff --git a/app/models/assignment.rb b/app/models/assignment.rb
index f6b943d45..c1d114f7b 100644
--- a/app/models/assignment.rb
+++ b/app/models/assignment.rb
@@ -192,17 +192,6 @@ def varying_rubrics_by_round?
end
#E2479
-# Checks if a specific user is already part of any team for this assignment.
-# This method queries the teams associated with this assignment and checks if the user is a member.
-# Params:
-# - user: The user to check for team membership.
-# Returns:
-# - true if the user is part of any team for this assignment.
-# - false otherwise.
-def is_participant_on_team?(user)
- # Join the teams and team users tables to check if a record exists for the user.
- teams.joins(:teams_users).exists?(teams_users: { user_id: user.id })
-end
# Validates if a user is eligible to join a team for the current assignment.
# This method ensures that:
@@ -214,7 +203,7 @@ def is_participant_on_team?(user)
# - A hash indicating the validation result:
# - { success: true } if the user can join the team.
# - { success: false, error: "Reason for failure" } if the user cannot join the team.
-def validate_team_participant_for_assignment?(user)
+def valid_team_participant_for_assignment?(user)
# Check if the user is already part of a team for this assignment.
if is_user_on_team?(user)
{ success: false, error: "This user is already assigned to a team for this assignment" }
@@ -229,17 +218,4 @@ def validate_team_participant_for_assignment?(user)
end
end
-# Retrieves all courses assigned to a specific instructor.
-# This method fetches courses for which the given user is listed as the instructor.
-# The courses are ordered alphabetically by their names for consistent display.
-# Params:
-# - user: The instructor user whose courses are to be fetched.
-# Returns:
-# - An ActiveRecord relation containing the courses assigned to the instructor.
-def self.fetch_courses_for_instructor(user)
- # Query for courses where the instructor ID matches the user's ID, ordered by course name.
- Course.where(instructor_id: user.id).order(:name)
-end
-
-
end
\ No newline at end of file
diff --git a/app/models/team.rb b/app/models/team.rb
index b387fe906..563532596 100644
--- a/app/models/team.rb
+++ b/app/models/team.rb
@@ -1,15 +1,16 @@
class Team < ApplicationRecord
has_many :signed_up_teams, dependent: :destroy
- has_many :teams_participants, dependent: :destroy
+ has_many :teams_users, dependent: :destroy
has_many :join_team_requests, dependent: :destroy
has_one :team_node, foreign_key: :node_object_id, dependent: :destroy
- has_many :users, through: :teams_participants
+ has_many :users, through: :teams_users
has_many :bids, dependent: :destroy
has_many :participants
- belongs_to :assignment
+ belongs_to :assignment, optional: true, foreign_key: 'parent_id'
+ validates :name, presence: true
attr_accessor :max_participants
scope :find_team_for_assignment_and_user, lambda { |assignment_id, user_id|
- joins(:teams_participants).where('teams.parent_id = ? AND teams_participants.user_id = ?', assignment_id, user_id)
+ joins(:teams_users).where('teams.parent_id = ? AND teams_users.user_id = ?', assignment_id, user_id)
}
# TODO Team implementing Teams controller and model should implement this method better.
# TODO partial implementation here just for the functionality needed for join_team_tequests controller
@@ -21,7 +22,29 @@ def full?
false
end
end
+ def add_member(user, _assignment_id = nil)
+ raise "The user #{user.name} is already a member of the team #{name}" if user?(user)
+ can_add_member = false
+ unless full?
+ can_add_member = true
+ t_user = TeamsUser.create(user_id: user.id, team_id: id)
+ parent = TeamNode.find_by(node_object_id: id)
+ TeamUserNode.create(parent_id: parent.id, node_object_id: t_user.id)
+ add_participant(parent_id, user)
+ ExpertizaLogger.info LoggerMessage.new('Model:Team', user.name, "Added member to the team #{id}")
+ end
+ can_add_member
+ end
+# Generate the team name
+def self.generate_team_name(_team_name_prefix = '')
+ last_team = Team.where('name LIKE ?', "#{_team_name_prefix} Team_%")
+ .order("CAST(SUBSTRING(name, LENGTH('#{_team_name_prefix} Team_') + 1) AS UNSIGNED) DESC")
+ .first
+ counter = last_team ? last_team.name.scan(/\d+/).first.to_i + 1 : 1
+ team_name = "#{_team_name_prefix} Team_#{counter}"
+ team_name
+end
# Adds a user to the team while handling potential errors such as duplicate membership.
# This is a wrapper around the `add_member` method with error handling.
# Params:
@@ -87,10 +110,10 @@ def participant_present?(user)
# Participants are derived from the users belonging to the team.
# Returns:
# - An array of participant objects.
-def participants
+def participants_in_team
users.where(parent_id: parent_id || current_user_id).flat_map(&:participants)
end
-alias get_participants participants
+alias get_participants participants_in_team
# Retrieves the full names of all users in the team.
# Returns:
@@ -107,7 +130,7 @@ def participant_full_names
# - The number of non-mentor members in the team.
def self.size(team_id)
count = 0
- team_participants = TeamsParticipant.where(team_id: team_id)
+ team_participants = TeamsUser.where(team_id: team_id)
# Exclude mentors from the count.
team_participants.each do |team_participants|
@@ -123,7 +146,7 @@ def self.size(team_id)
# - parent: The parent entity (assignment or course) associated with the team.
# - name: The name of the team to check for uniqueness.
# - team_type: The type of team (e.g., 'Assignment', 'Course').
-def self.check_for_existing(parent, name, team_type)
+def self.check_for_existing_team(parent, name, team_type)
existing_teams = Object.const_get("#{team_type}Team").where(parent_id: parent.id, name: name)
# Raise an error if any team with the same name is found.
@@ -139,9 +162,9 @@ def self.check_for_existing(parent, name, team_type)
# Returns:
# - A list of team IDs for the user within the specified assignment.
def self.find_team_participants(assignment_id, user_id)
- TeamsParticipant.joins('INNER JOIN teams ON teams_participants.team_id = teams.id')
+ TeamsUser.joins('INNER JOIN teams ON teams_users.team_id = teams.id')
.select('teams.id as team_id')
- .where('teams.parent_id = ? AND teams_participants.user_id = ?', assignment_id, user_id)
+ .where('teams.parent_id = ? AND teams_users.user_id = ?', assignment_id, user_id)
end
end
\ No newline at end of file
diff --git a/app/models/teams_participants.rb b/app/models/teams_participants.rb
index 5774bf4d2..ba9197f02 100644
--- a/app/models/teams_participants.rb
+++ b/app/models/teams_participants.rb
@@ -1,115 +1,31 @@
-class TeamsParticipant < ApplicationRecord
+class TeamsUser < ApplicationRecord
belongs_to :user
belongs_to :team
has_one :team_user_node, foreign_key: 'node_object_id', dependent: :destroy
- has_paper_trail
+ # Retrieves the team members for a specific team.
def get_team_members(team_id)
team_members = TeamsUser.where('team_id = ?', team_id)
user_ids = team_members.pluck(:user_id)
- users = User.where(id: user_ids)
-
- return users
+ User.where(id: user_ids)
end
- # Removes entry in the TeamUsers table for the given user and given team id
+ # Removes entry in the TeamUsers table for the given user and team ID.
def self.remove_team(user_id, team_id)
- team_user = TeamsUser.where('user_id = ? and team_id = ?', user_id, team_id).first
+ team_user = TeamsUser.find_by(user_id: user_id, team_id: team_id)
team_user&.destroy
end
#E2479
-# Retrieves the name of the user associated with the team member.
-# Optionally appends ' (Mentor)' if the user is a mentor.
-# Params:
-# - ip_address (optional): The IP address of the user (used for logging or contextual name resolution).
-# Returns:
-# - The name of the user, with '(Mentor)' appended if the user is a mentor.
-def display_name(ip_address = nil)
- participant_name = user.name(ip_address)
- participant_name += ' (Mentor)' if MentorManagement.user_a_mentor?(user)
- participant_name
-<<<<<<< HEAD
-=======
-end
-
-# Deletes multiple team members (identified by their IDs) in bulk.
-# This method is used for efficient removal of multiple TeamsUser records.
-# Params:
-# - team_user_ids: An array of IDs of the TeamsUser records to be deleted.
-# Returns:
-# - The number of records deleted (implicit return from destroy_all).
-def self.bulk_delete(team_user_ids)
- # Delete all TeamsUser records matching the provided IDs.
- where(id: team_user_ids).destroy_all
-end
-
-# Checks whether a specific user is a member of a given team.
-# Params:
-# - user_id: The ID of the user to check.
-# - team_id: The ID of the team to check for membership.
-# Returns:
-# - true if the user is a member of the team.
-# - false otherwise.
-def self.participant_part_of_team?(user_id, team_id)
- # Check if a TeamsUser record exists with the specified user and team IDs.
- exists?(user_id: user_id, team_id: team_id)
-end
-
-# Checks whether a team is empty (i.e., has no members).
-# Params:
-# - team_id: The ID of the team to check.
-# Returns:
-# - true if the team has no members.
-# - false otherwise.
-def self.is_team_empty?(team_id)
- # Retrieve all members of the team.
- team_members = TeamsUser.where('team_id = ?', team_id)
-
- # Return true if the team has no members; false otherwise.
- team_members.blank?
-end
-
-
->>>>>>> f97c630072c606b25b1317e435f0b97733fd10a8
-end
-
-# Deletes multiple team members (identified by their IDs) in bulk.
-# This method is used for efficient removal of multiple `TeamsUser` records.
-# Params:
-# - team_user_ids: An array of IDs of the `TeamsUser` records to be deleted.
-# Returns:
-# - The number of records deleted (implicit return from `destroy_all`).
-def self.bulk_delete(team_user_ids)
- # Delete all `TeamsUser` records matching the provided IDs.
- where(id: team_user_ids).destroy_all
-end
-
-# Checks whether a specific user is a member of a given team.
-# Params:
-# - user_id: The ID of the user to check.
-# - team_id: The ID of the team to check for membership.
-# Returns:
-# - true if the user is a member of the team.
-# - false otherwise.
-def self.participant_part_of_team?(user_id, team_id)
- # Check if a `TeamsUser` record exists with the specified user and team IDs.
- exists?(user_id: user_id, team_id: team_id)
-end
-
-# Checks whether a team is empty (i.e., has no members).
-# Params:
-# - team_id: The ID of the team to check.
-# Returns:
-# - true if the team has no members.
-# - false otherwise.
-def self.is_team_empty?(team_id)
- # Retrieve all members of the team.
- team_members = TeamsUser.where('team_id = ?', team_id)
+ # Deletes multiple team members in bulk.
+ def self.bulk_delete_participants(team_user_ids)
+ where(id: team_user_ids).destroy_all
+ end
- # Return true if the team has no members; false otherwise.
- team_members.blank?
+ # Custom name display for mentors.
+ def name(ip_address = nil)
+ name = user.name(ip_address)
+ name += ' (Mentor)' if MentorManagement.user_a_mentor?(user)
+ name
+ end
end
-
-
-end
\ No newline at end of file
diff --git a/spec/controllers/teams_participants_controller_spec.rb b/spec/controllers/teams_participants_controller_spec.rb
new file mode 100644
index 000000000..96530960f
--- /dev/null
+++ b/spec/controllers/teams_participants_controller_spec.rb
@@ -0,0 +1,89 @@
+describe 'GET #auto_complete_for_participant_name' do
+ let(:team) { create(:team) }
+ let(:participant) { create(:user, name: 'John Doe') }
+
+ before do
+ allow(Team).to receive(:find).and_return(team)
+ allow(team).to receive(:get_possible_team_members).and_return([participant])
+ end
+
+ it 'fetches potential team members based on input' do
+ session[:team_id] = team.id
+ get :auto_complete_for_participant_name, params: { user: { name: 'John' } }
+ expect(response).to be_successful
+ expect(assigns(:potential_team_members)).to eq([participant])
+ end
+
+ it 'renders the auto-complete suggestions' do
+ session[:team_id] = team.id
+ get :auto_complete_for_participant_name, params: { user: { name: 'John' } }
+ expect(response.body).to include(participant.name)
+ end
+end
+describe 'POST #update_duties' do
+ let(:team_participant) { create(:teams_participant) }
+
+ it 'updates the duty of a team member' do
+ post :update_duties, params: {
+ teams_user_id: team_participant.id,
+ teams_user: { duty_id: 2 },
+ participant_id: team_participant.user_id
+ }
+ expect(team_participant.reload.duty_id).to eq(2)
+ expect(response).to redirect_to(controller: 'student_teams', action: 'view', student_id: team_participant.user_id)
+ end
+end
+describe 'GET #list_participants' do
+ let(:team) { create(:team) }
+ let(:assignment) { create(:assignment, id: team.parent_id) }
+ let(:participant) { create(:teams_participant, team: team) }
+
+ it 'lists participants of a team' do
+ get :list_participants, params: { id: team.id }
+ expect(assigns(:team)).to eq(team)
+ expect(assigns(:assignment)).to eq(assignment)
+ expect(assigns(:team_participants)).to include(participant)
+ end
+end
+describe 'GET #add_new_participant' do
+ let(:team) { create(:team) }
+
+ it 'renders the form for adding a new participant' do
+ get :add_new_participant, params: { id: team.id }
+ expect(response).to render_template(:add_new_participant)
+ expect(assigns(:team)).to eq(team)
+ end
+end
+describe 'POST #create_participant' do
+ let(:team) { create(:team) }
+ let(:participant) { create(:user) }
+
+ before do
+ allow(Team).to receive(:find).and_return(team)
+ allow(User).to receive(:find_by).and_return(participant)
+ allow(team).to receive(:add_member_with_handling).and_return(true)
+ end
+
+ it 'adds a participant to a team successfully' do
+ post :create_participant, params: { user: { name: participant.name }, id: team.id }
+ expect(flash[:notice]).to include(participant.name)
+ expect(response).to redirect_to(controller: 'teams', action: 'list', id: team.parent_id)
+ end
+
+ it 'shows an error when the participant is invalid' do
+ allow(team).to receive(:add_member_with_handling).and_return(false)
+ post :create_participant, params: { user: { name: participant.name }, id: team.id }
+ expect(flash[:error]).to include('maximum number of members')
+ end
+end
+describe 'DELETE #delete_participant' do
+ let(:team) { create(:team) }
+ let(:participant) { create(:user) }
+ let(:team_participant) { create(:teams_participant, team: team, user: participant) }
+
+ it 'deletes the participant from the team' do
+ delete :delete_participant, params: { id: team_participant.id }
+ expect(flash[:notice]).to include(participant.name)
+ expect(response).to redirect_to(controller: 'teams', action: 'list', id: team.parent_id)
+ end
+end
diff --git a/spec/factories/factories.rb b/spec/factories/factories.rb
index eb06d7682..a2820c536 100644
--- a/spec/factories/factories.rb
+++ b/spec/factories/factories.rb
@@ -1,6 +1,22 @@
FactoryBot.define do
+ factory :user do
+ name { "John Doe" }
+ end
+
+ factory :team do
+ name { "Team A" }
+ parent_id { 1 }
+ end
+
+ factory :teams_participant do
+ association :user
+ association :team
+ duty_id { 1 }
+ end
+
+ factory :assignment do
+ name { "Assignment 1" }
+ end
+ end
-
-
-end
From 4f3b30869b3ca4a30c46369145b3deaa96d4aba5 Mon Sep 17 00:00:00 2001
From: manideepika21
Date: Tue, 3 Dec 2024 23:09:54 -0500
Subject: [PATCH 15/18] Added spec file to test the
teams_participants_controller
---
Gemfile | 2 +-
app/models/team.rb | 129 +-----------
.../teams_participants_controller_spec.rb | 184 +++++++++++-------
spec/factories/factories.rb | 19 +-
4 files changed, 120 insertions(+), 214 deletions(-)
diff --git a/Gemfile b/Gemfile
index 53202af81..1786836d0 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,7 +1,7 @@
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
-ruby '3.2.5'
+ruby '3.2.1'
gem 'mysql2', '~> 0.5.5'
gem 'puma', '~> 5.0'
diff --git a/app/models/team.rb b/app/models/team.rb
index 563532596..d0f6157c2 100644
--- a/app/models/team.rb
+++ b/app/models/team.rb
@@ -6,7 +6,7 @@ class Team < ApplicationRecord
has_many :users, through: :teams_users
has_many :bids, dependent: :destroy
has_many :participants
- belongs_to :assignment, optional: true, foreign_key: 'parent_id'
+ belongs_to :assignment
validates :name, presence: true
attr_accessor :max_participants
scope :find_team_for_assignment_and_user, lambda { |assignment_id, user_id|
@@ -22,7 +22,10 @@ def full?
false
end
end
- def add_member(user, _assignment_id = nil)
+
+ #E2479
+ #Check if a user is already a member of the team if not it adds the user
+ def add_participant(user, _assignment_id = nil)
raise "The user #{user.name} is already a member of the team #{name}" if user?(user)
can_add_member = false
@@ -36,23 +39,7 @@ def add_member(user, _assignment_id = nil)
end
can_add_member
end
-# Generate the team name
-def self.generate_team_name(_team_name_prefix = '')
- last_team = Team.where('name LIKE ?', "#{_team_name_prefix} Team_%")
- .order("CAST(SUBSTRING(name, LENGTH('#{_team_name_prefix} Team_') + 1) AS UNSIGNED) DESC")
- .first
- counter = last_team ? last_team.name.scan(/\d+/).first.to_i + 1 : 1
- team_name = "#{_team_name_prefix} Team_#{counter}"
- team_name
-end
- # Adds a user to the team while handling potential errors such as duplicate membership.
-# This is a wrapper around the `add_member` method with error handling.
-# Params:
-# - user: The user to be added to the team.
-# - parent_id: The ID of the parent entity (assignment or course).
-# Returns:
-# - true if the user was successfully added.
-# - A hash with an error message if the addition fails.
+
def add_participants_with_handling(user, parent_id)
begin
# Attempt to add the user to the team.
@@ -63,108 +50,4 @@ def add_participants_with_handling(user, parent_id)
{ success: false, error: "The user #{user.name} is already a member of the team #{name}" }
end
end
-
-# Adds a user to the team if they are not already a member and the team is not full.
-# Also creates the necessary team user and team node entries.
-# Params:
-# - user: The user to be added.
-# - _assignment_id (optional): The ID of the assignment (not used directly in this implementation).
-# Returns:
-# - true if the user was successfully added to the team.
-# - false if the team is full or if the user cannot be added.
-def add_participant(user, _assignment_id = nil)
- # Raise an error if the user is already a member of the team.
- raise "The user #{user.name} is already a member of the team #{name}" if member?(user)
-
- # Check if the team is not full before adding the user.
- if !full?
- # Create a relationship between the user and the team.
- team_user_relationship = TeamsParticipant.create(user_id: user.id, team_id: id)
-
- # Create the corresponding team user node in the hierarchy.
- team_node = TeamNode.find_by(node_object_id: id)
- TeamUserNode.create(parent_id: team_node.id, node_object_id: team_user_relationship.id)
-
- # Add the user as a participant for the parent assignment or course.
- add_participant_to_parent_entity(parent_id, user)
-
- # Log the successful addition of the user to the team.
- ExpertizaLogger.info LoggerMessage.new('Model:Team', user.name, "Added member to the team #{id}")
- true
- else
- false
- end
-end
-
-# Checks if the given user is already a member of the team.
-# Params:
-# - user: The user to check for membership.
-# Returns:
-# - true if the user is already a member of the team.
-# - false otherwise.
-def participant_present?(user)
- users.include?(user)
-end
-
-# Retrieves all participants associated with the team.
-# Participants are derived from the users belonging to the team.
-# Returns:
-# - An array of participant objects.
-def participants_in_team
- users.where(parent_id: parent_id || current_user_id).flat_map(&:participants)
-end
-alias get_participants participants_in_team
-
-# Retrieves the full names of all users in the team.
-# Returns:
-# - An array of strings representing the full names of the team members.
-def participant_full_names
- users.map(&:fullname)
-end
-alias author_names participant_full_names
-
-# Determines the current size of the team, excluding mentors.
-# Params:
-# - team_id: The ID of the team whose size is being checked.
-# Returns:
-# - The number of non-mentor members in the team.
-def self.size(team_id)
- count = 0
- team_participants = TeamsUser.where(team_id: team_id)
-
- # Exclude mentors from the count.
- team_participants.each do |team_participants|
- team_participants_name = team_participants.name
- count += 1 unless team_participants_name.include?(' (Mentor)')
- end
- count
-end
-
-# Checks if a team with the given name already exists for a parent entity (assignment or course).
-# Raises an error if a team with the same name already exists.
-# Params:
-# - parent: The parent entity (assignment or course) associated with the team.
-# - name: The name of the team to check for uniqueness.
-# - team_type: The type of team (e.g., 'Assignment', 'Course').
-def self.check_for_existing_team(parent, name, team_type)
- existing_teams = Object.const_get("#{team_type}Team").where(parent_id: parent.id, name: name)
-
- # Raise an error if any team with the same name is found.
- unless existing_teams.empty?
- raise TeamExistsError, "The team name #{name} is already in use."
- end
-end
-
-# Retrieves all teams that a specific user belongs to for a given assignment.
-# Params:
-# - assignment_id: The ID of the assignment.
-# - user_id: The ID of the user.
-# Returns:
-# - A list of team IDs for the user within the specified assignment.
-def self.find_team_participants(assignment_id, user_id)
- TeamsUser.joins('INNER JOIN teams ON teams_users.team_id = teams.id')
- .select('teams.id as team_id')
- .where('teams.parent_id = ? AND teams_users.user_id = ?', assignment_id, user_id)
-end
-
end
\ No newline at end of file
diff --git a/spec/controllers/teams_participants_controller_spec.rb b/spec/controllers/teams_participants_controller_spec.rb
index 96530960f..c0b3f8257 100644
--- a/spec/controllers/teams_participants_controller_spec.rb
+++ b/spec/controllers/teams_participants_controller_spec.rb
@@ -1,89 +1,129 @@
-describe 'GET #auto_complete_for_participant_name' do
- let(:team) { create(:team) }
- let(:participant) { create(:user, name: 'John Doe') }
+require 'rails_helper'
- before do
- allow(Team).to receive(:find).and_return(team)
- allow(team).to receive(:get_possible_team_members).and_return([participant])
- end
+RSpec.describe Api::V1::TeamsParticipantsController, type: :controller do
+ let(:student_role) { create(:role, :student) }
+ let(:instructor_role) { create(:role, :instructor) }
+ let(:instructor) { create(:user, role: create(:role, :instructor)) }
+ let(:course) { create(:course, instructor_id: instructor.id) }
+ let(:user) { create(:user, role: student_role) }
+ let(:ta) { create(:teaching_assistant) }
+ let(:team) { create(:team, parent_id: create(:assignment).id) }
+ let(:participant) { create(:user, name: 'Test Participant') }
+ let(:team_participant) { create(:teams_user, user: participant, team: team) }
- it 'fetches potential team members based on input' do
- session[:team_id] = team.id
- get :auto_complete_for_participant_name, params: { user: { name: 'John' } }
- expect(response).to be_successful
- expect(assigns(:potential_team_members)).to eq([participant])
- end
+ describe '#action_allowed?' do
+ context 'when action is update_duties' do
+ it 'allows access for students' do
+ allow(controller).to receive(:current_user_has_student_privileges?).and_return(true)
+ allow(controller).to receive(:params).and_return({ action: 'update_duties' })
+ expect(controller.action_allowed?).to be true
+ end
- it 'renders the auto-complete suggestions' do
- session[:team_id] = team.id
- get :auto_complete_for_participant_name, params: { user: { name: 'John' } }
- expect(response.body).to include(participant.name)
+ it 'denies access for non-students' do
+ allow(controller).to receive(:current_user_has_student_privileges?).and_return(false)
+ allow(controller).to receive(:params).and_return({ action: 'update_duties' })
+ expect(controller.action_allowed?).to be false
+ end
+ end
+
+ context 'when action is not update_duties' do
+ it 'allows access for TAs' do
+ allow(controller).to receive(:current_user_has_ta_privileges?).and_return(true)
+ allow(controller).to receive(:params).and_return({ action: 'list_participants' })
+ expect(controller.action_allowed?).to be true
+ end
+ end
end
-end
-describe 'POST #update_duties' do
- let(:team_participant) { create(:teams_participant) }
-
- it 'updates the duty of a team member' do
- post :update_duties, params: {
- teams_user_id: team_participant.id,
- teams_user: { duty_id: 2 },
- participant_id: team_participant.user_id
+
+ describe '#update_duties' do
+ it 'updates the duties for the participant' do
+ # Mock the TeamsUser object
+ allow(TeamsUser).to receive(:find).with('1').and_return(team_participant)
+ allow(team_participant).to receive(:update_attribute).with(:team_id, '2').and_return('OK')
+
+ # Prepare request and session parameters
+ request_params = {
+ teams_user_id: '1', # ID of the TeamsUser to update
+ teams_user: { team_id: '2' }, # Attribute to update
+ participant_id: '1' # Participant ID for redirection
}
- expect(team_participant.reload.duty_id).to eq(2)
- expect(response).to redirect_to(controller: 'student_teams', action: 'view', student_id: team_participant.user_id)
- end
-end
-describe 'GET #list_participants' do
- let(:team) { create(:team) }
- let(:assignment) { create(:assignment, id: team.parent_id) }
- let(:participant) { create(:teams_participant, team: team) }
-
- it 'lists participants of a team' do
- get :list_participants, params: { id: team.id }
- expect(assigns(:team)).to eq(team)
- expect(assigns(:assignment)).to eq(assignment)
- expect(assigns(:team_participants)).to include(participant)
- end
-end
-describe 'GET #add_new_participant' do
- let(:team) { create(:team) }
+ user_session = { user: stub_current_user(student, student.role.name, student.role) }
- it 'renders the form for adding a new participant' do
- get :add_new_participant, params: { id: team.id }
- expect(response).to render_template(:add_new_participant)
- expect(assigns(:team)).to eq(team)
+ # Perform the request
+ get :update_duties, params: request_params, session: user_session
+
+ # Expectations
+ expect(response).to redirect_to('/student_teams/view?student_id=1')
+ expect(team_participant).to have_received(:update_attribute).with(:team_id, '2')
end
end
-describe 'POST #create_participant' do
- let(:team) { create(:team) }
- let(:participant) { create(:user) }
-
- before do
- allow(Team).to receive(:find).and_return(team)
- allow(User).to receive(:find_by).and_return(participant)
- allow(team).to receive(:add_member_with_handling).and_return(true)
+
+
+ describe '#list_participants' do
+ it 'assigns participants and renders the view' do
+ assignment = create(:assignment, id: team.parent_id)
+ create_list(:teams_user, 5, team: team)
+
+ get :list_participants, params: { id: team.id, page: 1 }
+
+ expect(assigns(:team_participants).size).to eq(5)
+ expect(assigns(:team)).to eq(team)
+ expect(assigns(:assignment)).to eq(assignment)
+ end
end
- it 'adds a participant to a team successfully' do
- post :create_participant, params: { user: { name: participant.name }, id: team.id }
- expect(flash[:notice]).to include(participant.name)
- expect(response).to redirect_to(controller: 'teams', action: 'list', id: team.parent_id)
+ describe '#add_new_participant' do
+ it 'renders the form for adding a new participant' do
+ get :add_new_participant, params: { id: team.id }
+
+ expect(response).to render_template(:add_new_participant)
+ end
end
- it 'shows an error when the participant is invalid' do
- allow(team).to receive(:add_member_with_handling).and_return(false)
- post :create_participant, params: { user: { name: participant.name }, id: team.id }
- expect(flash[:error]).to include('maximum number of members')
+ describe '#create_participant' do
+ context 'when participant is valid' do
+ it 'adds the participant and redirects' do
+ allow(controller).to receive(:find_participant_by_name).and_return(participant)
+ allow(controller).to receive(:find_team_by_id).and_return(team)
+ allow(controller).to receive(:validate_participant_and_team).and_return(true)
+
+ post :create_participant, params: { user: { name: participant.name }, id: team.id }
+
+ expect(response).to redirect_to(controller: 'teams', action: 'list', id: team.parent_id)
+ end
+ end
+
+ context 'when participant is invalid' do
+ it 'flashes an error and redirects back' do
+ allow(controller).to receive(:find_participant_by_name).and_return(nil)
+
+ post :create_participant, params: { user: { name: 'Invalid User' }, id: team.id }
+
+ expect(flash[:error]).to be_present
+ expect(response).to redirect_to(root_path)
+ end
+ end
end
-end
-describe 'DELETE #delete_participant' do
- let(:team) { create(:team) }
- let(:participant) { create(:user) }
- let(:team_participant) { create(:teams_participant, team: team, user: participant) }
- it 'deletes the participant from the team' do
+ describe '#delete_participant' do
+ it 'deletes the participant and redirects' do
+ Rails.logger.debug "Creating test data..."
+ assignment = create(:assignment)
+ parent_team = create(:team, parent_id: assignment.id)
+ team.update!(parent_id: parent_team.id)
+ participant = create(:user)
+ team_participant = create(:teams_user, team: team, user: participant)
+
+ Rails.logger.debug "TeamsUser before deletion: #{TeamsUser.exists?(team_participant.id)}"
+
delete :delete_participant, params: { id: team_participant.id }
- expect(flash[:notice]).to include(participant.name)
- expect(response).to redirect_to(controller: 'teams', action: 'list', id: team.parent_id)
+
+ Rails.logger.debug "TeamsUser after deletion: #{TeamsUser.exists?(team_participant.id)}"
+
+ expect(TeamsUser.exists?(team_participant.id)).to be_falsey # Verify deletion
+ expect(response).to redirect_to(controller: 'teams', action: 'list', id: parent_team.id) # Verify redirection
end
end
+
+
+end
diff --git a/spec/factories/factories.rb b/spec/factories/factories.rb
index a2820c536..eb7066da9 100644
--- a/spec/factories/factories.rb
+++ b/spec/factories/factories.rb
@@ -1,22 +1,5 @@
FactoryBot.define do
- factory :user do
- name { "John Doe" }
- end
-
- factory :team do
- name { "Team A" }
- parent_id { 1 }
- end
-
- factory :teams_participant do
- association :user
- association :team
- duty_id { 1 }
- end
-
- factory :assignment do
- name { "Assignment 1" }
- end
+
end
From e6b39e79b653f8ecd7b7d56e90a813641a9e4249 Mon Sep 17 00:00:00 2001
From: manideepika21
Date: Wed, 4 Dec 2024 01:56:16 -0500
Subject: [PATCH 16/18] Changed the function name in assignment model
---
app/models/assignment.rb | 2 +-
app/models/{teams_participants.rb => teams_users.rb} | 0
2 files changed, 1 insertion(+), 1 deletion(-)
rename app/models/{teams_participants.rb => teams_users.rb} (100%)
diff --git a/app/models/assignment.rb b/app/models/assignment.rb
index c1d114f7b..dc6888ca1 100644
--- a/app/models/assignment.rb
+++ b/app/models/assignment.rb
@@ -203,7 +203,7 @@ def varying_rubrics_by_round?
# - A hash indicating the validation result:
# - { success: true } if the user can join the team.
# - { success: false, error: "Reason for failure" } if the user cannot join the team.
-def valid_team_participant_for_assignment?(user)
+def valid_team_participant?(user)
# Check if the user is already part of a team for this assignment.
if is_user_on_team?(user)
{ success: false, error: "This user is already assigned to a team for this assignment" }
diff --git a/app/models/teams_participants.rb b/app/models/teams_users.rb
similarity index 100%
rename from app/models/teams_participants.rb
rename to app/models/teams_users.rb
From 98eeee32022778ab58e1e254a4ef568c8af17fbd Mon Sep 17 00:00:00 2001
From: manideepika21
Date: Wed, 4 Dec 2024 01:59:10 -0500
Subject: [PATCH 17/18] Added logic to check if the user is already in the team
---
app/models/assignment.rb | 12 ++++++++++--
1 file changed, 10 insertions(+), 2 deletions(-)
diff --git a/app/models/assignment.rb b/app/models/assignment.rb
index dc6888ca1..4a4401e88 100644
--- a/app/models/assignment.rb
+++ b/app/models/assignment.rb
@@ -192,7 +192,15 @@ def varying_rubrics_by_round?
end
#E2479
-
+#check if the user is on the team
+def user_on_team?(user)
+ teams = self.teams
+ users = []
+ teams.each do |team|
+ users << team.users
+ end
+ users.flatten.include? user
+end
# Validates if a user is eligible to join a team for the current assignment.
# This method ensures that:
# - The user is not already part of another team for this assignment.
@@ -205,7 +213,7 @@ def varying_rubrics_by_round?
# - { success: false, error: "Reason for failure" } if the user cannot join the team.
def valid_team_participant?(user)
# Check if the user is already part of a team for this assignment.
- if is_user_on_team?(user)
+ if user_on_team?(user)
{ success: false, error: "This user is already assigned to a team for this assignment" }
# Check if the user is a registered participant in the assignment.
From 51af1ccbd701222ed2c70577dfca37fc0a55622e Mon Sep 17 00:00:00 2001
From: manideepika21
Date: Wed, 4 Dec 2024 02:37:46 -0500
Subject: [PATCH 18/18] Modified variable names in
teams_participants_controller
---
.../api/v1/teams_participants_controller.rb | 30 ++++++++-----------
app/models/course.rb | 11 ++++++-
app/models/teams_users.rb | 2 +-
3 files changed, 24 insertions(+), 19 deletions(-)
diff --git a/app/controllers/api/v1/teams_participants_controller.rb b/app/controllers/api/v1/teams_participants_controller.rb
index c500ee01a..5bf4439d6 100644
--- a/app/controllers/api/v1/teams_participants_controller.rb
+++ b/app/controllers/api/v1/teams_participants_controller.rb
@@ -54,37 +54,33 @@ def add_new_participant
# Fetch the team for which a participant is to be added.
@team = Team.find(params[:id])
end
+ #Deletes the selected participant
+ def delete_selected_participant
+ @teams_user = TeamsUser.find(params[:id])
+ parent_id = Team.find(@teams_user.team_id).parent_id
+ @user = User.find(@teams_user.user_id)
+ @teams_user.destroy
+ undo_link("The team user \"#{@user.name}\" has been successfully removed. ")
+ redirect_to controller: 'teams', action: 'list', id: parent_id
+ end
# Adds a new participant to a team after validation.
def create_participant
# Find the user by their name from the input.
find_participant = find_participant_by_name
-
# Fetch the team using the provided ID.
current_team = find_team_by_id
-
if validate_participant_and_team(participant, team)
- if team.add_participants_with_handling(participant, team.parent_id)
+ if team.add_participants_with_validation(participant, team.parent_id)
undo_link("The participant \"#{participant.name}\" has been successfully added to \"#{team.name}\".")
else
flash[:error] = 'This team already has the maximum number of members.'
end
end
-
# Redirect to the list of teams for the parent assignment or course.
redirect_to controller: 'teams', action: 'list', id: current_team.parent_id
end
-
- #Deletes the selected participant
- def delete_selected_participant
- @teams_user = TeamsUser.find(params[:id])
- parent_id = Team.find(@teams_user.team_id).parent_id
- @user = User.find(@teams_user.user_id)
- @teams_user.destroy
- undo_link("The team user \"#{@user.name}\" has been successfully removed. ")
- redirect_to controller: 'teams', action: 'list', id: parent_id
- end
-
+
private
# Helper method to find a user by their name.
@@ -128,11 +124,11 @@ def validate_participant_and_team(participant, team)
def add_participant_to_team(find_participant, team)
# Add the participant to the team and handle the outcome.
addition_result = find_team_by_id.add_participant(find_participant, team.parent_id)
- handle_addition_result(find_participant, team, addition_result)
+ process_participant_addition_result(find_participant, team, addition_result)
end
# Handles the result of adding a participant to the team.
- def handle_addition_result(find_participant, team, addition_result)
+ def process_participant_addition_result(find_participant, team, addition_result)
if addition_result == false
flash[:error] = 'This team already has the maximum number of members.'
else
diff --git a/app/models/course.rb b/app/models/course.rb
index f2874d43d..04992ddbf 100644
--- a/app/models/course.rb
+++ b/app/models/course.rb
@@ -50,6 +50,15 @@ def copy_course
new_course.save
end
#E2479
+ #checks if the user is in the team
+ def user_on_team?(user)
+ teams = self.teams
+ users = []
+ teams.each do |team|
+ users << team.users
+ end
+ users.flatten.include? user
+ end
# Checks if a user is eligible to join a specific team for a course.
# This method ensures that:
# - The user is not already a member of another team for the course.
@@ -62,7 +71,7 @@ def copy_course
# - { success: false, error: "Reason for failure" } if the user cannot be added.
def valid_team_participant?(user)
# Check if the user is already a member of another team for the same course.
- if already_on_team?(user)
+ if user_on_team?(user)
{ success: false, error: "This user is already assigned to a team for this course" }
# Check if the user is a participant in the course associated with this team.
diff --git a/app/models/teams_users.rb b/app/models/teams_users.rb
index ba9197f02..0232dd8f2 100644
--- a/app/models/teams_users.rb
+++ b/app/models/teams_users.rb
@@ -18,7 +18,7 @@ def self.remove_team(user_id, team_id)
#E2479
# Deletes multiple team members in bulk.
- def self.bulk_delete_participants(team_user_ids)
+ def self.delete_multiple_participants(team_user_ids)
where(id: team_user_ids).destroy_all
end