Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
38baf8f
original model and model spec files
TiptonG Nov 17, 2024
d061378
dos2unix + test debugging
TiptonG Nov 17, 2024
255e213
Test fix attempt
TiptonG Nov 17, 2024
db3b576
added import for factory bot dependencies
Grochocinski Nov 17, 2024
f38d3e6
Fixing the factory formatting
TiptonG Nov 18, 2024
14c728c
Modularize self.feedback_response_map
TiptonG Nov 18, 2024
903cdbb
Fix naming issue
TiptonG Nov 27, 2024
bdccaaf
Add Comments
TiptonG Nov 28, 2024
7dbf99e
Attempted rework of self.varying_rubrics_report
TiptonG Nov 28, 2024
95ed78f
Modified feedback_response_map.rb
ssdaniel1824 Dec 2, 2024
0e1b78a
Modified feedback_response_map.rb using RuboCop
ssdaniel1824 Dec 2, 2024
15d0ef3
fixed invalid formatting for factory data
Grochocinski Dec 3, 2024
02bc923
added missing `def`s for private functions
Grochocinski Dec 3, 2024
531e608
added missing fields to response_maps schema
Grochocinski Dec 3, 2024
4574843
Imported missing required files from expertiza/expertiza
Grochocinski Dec 3, 2024
6253fce
Updated factories and tests to (mostly) run tests without errors
Grochocinski Dec 3, 2024
7d9b20d
fixed incorrect author variable reference
Grochocinski Dec 3, 2024
a622b74
adapted test cases to match reimplementation-back-end
Grochocinski Dec 3, 2024
e3ba300
fixed invalid author return param, fixed calling order on a non-activ…
Grochocinski Dec 3, 2024
97e0a81
added TODO comments for unimplemented functionality in tests
Grochocinski Dec 3, 2024
2295715
Update comments in self.feedback_response_report
TiptonG Dec 3, 2024
85b09bb
Rubocop offenses
ssdaniel1824 Dec 3, 2024
58f376f
dos2unix + removing "next" before "unless"
TiptonG Dec 3, 2024
2d39d9c
Swap to next if from unless
TiptonG Dec 3, 2024
9e7a594
fixed all spec test rubocop violations except for blocklength
Grochocinski Dec 3, 2024
3bd5afc
disabled blocklength check for spec test block
Grochocinski Dec 3, 2024
e7d3e0a
fixed rubocop violations in both factory files
Grochocinski Dec 3, 2024
5c6b386
Fixing rubocop issues
TiptonG Dec 3, 2024
a736574
Update feedback_response_report varying conditional
TiptonG Dec 4, 2024
423dc2e
Removing unnecessary comment
TiptonG Dec 4, 2024
6db8407
Merge pull request #1 from ssdaniel1824/splitting_method
TiptonG Dec 4, 2024
87f9533
Merge pull request #2 from ssdaniel1824/original_content
ssdaniel1824 Dec 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions app/models/analytic/assignment_team_analytic.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# require 'analytic/response_analytic'
module AssignmentTeamAnalytic
#======= general ==========#
def num_participants
participants.count
end

def num_reviews
responses.count
end

#========== score ========#
def average_review_score
if num_reviews == 0
0
else
review_scores.inject(:+).to_f / num_reviews
end
end

def max_review_score
review_scores.max
end

def min_review_score
review_scores.min
end

#======= word count =======#
def total_review_word_count
review_word_counts.inject(:+)
end

def average_review_word_count
if num_reviews == 0
0
else
total_review_word_count.to_f / num_reviews
end
end

def max_review_word_count
review_word_counts.max
end

def min_review_word_count
review_word_counts.min
end

#===== character count ====#
def total_review_character_count
review_character_counts.inject(:+)
end

def average_review_character_count
if num_reviews == 0
0
else
total_review_character_count.to_f / num_reviews
end
end

def max_review_character_count
review_character_counts.max
end

def min_review_character_count
review_character_counts.min
end

def review_character_counts
list = []
responses.each do |response|
list << response.total_character_count
end
if list.empty?
[0]
else
list
end
end

# return an array containing the score of all the reviews
def review_scores
list = []
responses.each do |response|
list << response.average_score
end
if list.empty?
[0]
else
list
end
end

def review_word_counts
list = []
responses.each do |response|
list << response.total_word_count
end
if list.empty?
[0]
else
list
end
end
end
289 changes: 289 additions & 0 deletions app/models/assignment_team.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,289 @@
class AssignmentTeam < Team
require File.dirname(__FILE__) + '/analytic/assignment_team_analytic'
include AssignmentTeamAnalytic
# include Scoring

belongs_to :assignment, class_name: 'Assignment', foreign_key: 'parent_id'
has_many :review_mappings, class_name: 'ReviewResponseMap', foreign_key: 'reviewee_id'
has_many :review_response_maps, foreign_key: 'reviewee_id'
has_many :responses, through: :review_response_maps, foreign_key: 'map_id'
# START of contributor methods, shared with AssignmentParticipant

# Added for E1973, Team reviews.
# Some methods prompt a reviewer for a user id. This method just returns the user id of the first user in the team
# This is a very hacky way to deal with very complex functionality but the reasoning is this:
# The reason this is being added is to give ReviewAssignment#reject_own_submission a way to reject the submission
# Of the reviewer. If there are team reviews, there must be team submissions, so any team member's user id will do.
# Hopefully, this logic applies if there are other situations where reviewer.user_id was called
# EDIT: A situation was found which differs slightly. If the current user is on the team, we want to
# return that instead for instances where the code uses the current user.
def user_id
@current_user.id if !@current_user.nil? && users.include?(@current_user)
users.first.id
end

# E1973
# stores the current user so that we can check them when returning the user_id
def set_current_user(current_user)
@current_user = current_user
end

# Whether this team includes a given participant or not
def includes?(participant)
participants.include?(participant)
end

# Get the parent of this class=>Assignment
def parent_model
'Assignment'
end

def self.parent_model(id)
Assignment.find(id)
end

# Get the name of the class
def fullname
name
end

# Get the review response map
def review_map_type
'ReviewResponseMap'
end

# Prototype method to implement prototype pattern
def self.prototype
AssignmentTeam.new
end

# Use current object (AssignmentTeam) as reviewee and create the ReviewResponseMap record
def assign_reviewer(reviewer)
assignment = Assignment.find(parent_id)
raise 'The assignment cannot be found.' if assignment.nil?

ReviewResponseMap.create(reviewee_id: id, reviewer_id: reviewer.get_reviewer.id, reviewed_object_id: assignment.id, team_reviewing_enabled: assignment.team_reviewing_enabled)
end

# E-1973 If a team is being treated as a reviewer of an assignment, then they are the reviewer
def get_reviewer
self
end

# Evaluates whether any contribution by this team was reviewed by reviewer
# @param[in] reviewer AssignmentParticipant object
def reviewed_by?(reviewer)
ReviewResponseMap.where('reviewee_id = ? && reviewer_id = ? && reviewed_object_id = ?', id, reviewer.get_reviewer.id, assignment.id).count > 0
end

# Topic picked by the team for the assignment
# This method needs refactoring: it sounds like it returns a topic object but in fact it returns an id
def topic
SignedUpTeam.find_by(team_id: id, is_waitlisted: 0).try(:topic_id)
end

# Whether the team has submitted work or not
def has_submissions?
submitted_files.any? || submitted_hyperlinks.present?
end

# Get Participants of the team
def participants
users = self.users
participants = []
users.each do |user|
participant = AssignmentParticipant.find_by(user_id: user.id, parent_id: parent_id)
participants << participant unless participant.nil?
end
participants
end
alias get_participants participants

# Delete the team
def delete
if self[:type] == 'AssignmentTeam'
sign_up = SignedUpTeam.find_team_participants(parent_id.to_s).select { |p| p.team_id == id }
sign_up.each(&:destroy)
end
super
end

# Delete Review response map
def destroy
review_response_maps.each(&:destroy)
super
end

# Get the first member of the team
def self.first_member(team_id)
find_by(id: team_id).try(:participants).try(:first)
end

# Return the files residing in the directory of team submissions
# Main calling method to return the files residing in the directory of team submissions
def submitted_files(path = self.path)
files = []
files = files(path) if directory_num
files
end

# REFACTOR BEGIN:: functionality of import,export, handle_duplicate shifted to team.rb
# Import csv file to form teams directly
def self.import(row, assignment_id, options)
unless Assignment.find_by(id: assignment_id)
raise ImportError, 'The assignment with the id "' + assignment_id.to_s + "\" was not found. <a href='/assignment/new'>Create</a> this assignment?"
end

@assignment_team = prototype
Team.import(row, assignment_id, options, @assignment_team)
end

# Export the existing teams in a csv file
def self.export(csv, parent_id, options)
@assignment_team = prototype
Team.export(csv, parent_id, options, @assignment_team)
end

# REFACTOR END:: functionality of import, export handle_duplicate shifted to team.rb

# Copy the current Assignment team to the CourseTeam
def copy(course_id)
new_team = CourseTeam.create_team_and_node(course_id)
new_team.name = name
new_team.save
copy_members(new_team)
end

# Add Participants to the current Assignment Team
def add_participant(assignment_id, user)
return if AssignmentParticipant.find_by(parent_id: assignment_id, user_id: user.id)

AssignmentParticipant.create(parent_id: assignment_id, user_id: user.id, permission_granted: user.master_permission_granted)
end

def hyperlinks
submitted_hyperlinks.blank? ? [] : YAML.safe_load(submitted_hyperlinks)
end

# Appends the hyperlink to a list that is stored in YAML format in the DB
# @exception If is hyperlink was already there
# If it is an invalid URL

def files(directory)
files_list = Dir[directory + '/*']
files = []

files_list.each do |file|
if File.directory?(file)
dir_files = files(file)
dir_files.each { |f| files << f }
end
files << file
end
files
end

def submit_hyperlink(hyperlink)
hyperlink.strip!
raise 'The hyperlink cannot be empty!' if hyperlink.empty?

hyperlink = 'http://' + hyperlink unless hyperlink.start_with?('http://', 'https://')
# If not a valid URL, it will throw an exception
response_code = Net::HTTP.get_response(URI(hyperlink))
raise "HTTP status code: #{response_code}" if response_code =~ /[45][0-9]{2}/

hyperlinks = self.hyperlinks
hyperlinks << hyperlink
self.submitted_hyperlinks = YAML.dump(hyperlinks)
save
end

# Note: This method is not used yet. It is here in the case it will be needed.
# @exception If the index does not exist in the array

def remove_hyperlink(hyperlink_to_delete)
hyperlinks = self.hyperlinks
hyperlinks.delete(hyperlink_to_delete)
self.submitted_hyperlinks = YAML.dump(hyperlinks)
save
end

# return the team given the participant
def self.team(participant)
return nil if participant.nil?

team = nil
teams_users = TeamsUser.where(user_id: participant.user_id)
return nil unless teams_users

teams_users.each do |teams_user|
if teams_user.team_id == nil
next
end
team = Team.find(teams_user.team_id)
return team if team.parent_id == participant.parent_id
end
nil
end

# Export the fields
def self.export_fields(options)
fields = []
fields.push('Team Name')
fields.push('Team members') if options[:team_name] == 'false'
fields.push('Assignment Name')
end

# Remove a team given the team id
def self.remove_team_by_id(id)
old_team = AssignmentTeam.find(id)
old_team.destroy unless old_team.nil?
end

# Get the path of the team directory
def path
assignment.path + '/' + directory_num.to_s
end

# Set the directory num for this team
def set_student_directory_num
return if directory_num && (directory_num >= 0)

max_num = AssignmentTeam.where(parent_id: parent_id).order('directory_num desc').first.directory_num
dir_num = max_num ? max_num + 1 : 0
update_attributes(directory_num: dir_num)
end

def received_any_peer_review?
ResponseMap.where(reviewee_id: id, reviewed_object_id: parent_id).any?
end

# Returns the most recent submission of the team
def most_recent_submission
assignment = Assignment.find(parent_id)
SubmissionRecord.where(team_id: id, assignment_id: assignment.id).order(updated_at: :desc).first
end

# E-1973 gets the participant id of the currently logged in user, given their user id
# this method assumes that the team is the reviewer since it would be called on
# AssignmentParticipant otherwise
def get_logged_in_reviewer_id(current_user_id)
participants.each do |participant|
return participant.id if participant.user.id == current_user_id
end
nil
end

# determines if the team contains a participant who is currently logged in
def current_user_is_reviewer?(current_user_id)
get_logged_in_reviewer_id(current_user_id) != nil
end

# E2121 Refractor create_new_team
def create_new_team(user_id, signuptopic)
t_user = TeamsUser.create(team_id: id, user_id: user_id)
SignedUpTeam.create(topic_id: signuptopic.id, team_id: id, is_waitlisted: 0)
parent = TeamNode.create(parent_id: signuptopic.assignment_id, node_object_id: id)
TeamUserNode.create(parent_id: parent.id, node_object_id: t_user.id)
end
end
Loading