Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

E1856. Allow reviewers to bid on what to review #1322

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b16f3cf
fix syntax error of assignment_team and add assignment_team test case
kuangwanjing Oct 29, 2018
a3ee567
review_bidding: set_priority
kuangwanjing Dec 3, 2018
985acfa
add review_bid model
kuangwanjing Dec 3, 2018
20815f0
controller method
emmableu Dec 4, 2018
1f60577
process review bidding-add a controller to handle the assignment requ…
kuangwanjing Dec 4, 2018
19d2503
Link to bidding review
Ychosen Dec 5, 2018
dce198d
Reformat link to bidding review
Ychosen Dec 5, 2018
43f93c6
Review bidding page done.
Ychosen Dec 5, 2018
44a0840
Commit before merge repo.
Ychosen Dec 5, 2018
eb3eb20
use team as review bidding unit
kuangwanjing Dec 6, 2018
77f347f
Merge branch 'krepo' into rbfrontend
Ychosen Dec 6, 2018
fb00b7a
Merge branch 'krepo' into rbfrontend
Ychosen Dec 6, 2018
8817c1e
Fix front_end due to reformat
Ychosen Dec 6, 2018
a26c69f
allow review bidding
kuangwanjing Dec 7, 2018
352fad9
fix config
kuangwanjing Dec 7, 2018
f08d132
Reformat front_end due to change.
Ychosen Dec 7, 2018
9cdf452
Merge branch 'krepo' into rbfrontend
Ychosen Dec 7, 2018
a09bccc
change parameter name
kuangwanjing Dec 7, 2018
2d01632
update webservice config
kuangwanjing Dec 7, 2018
654e00e
rollback the schema to avoid conflict
kuangwanjing Dec 7, 2018
bd9a17c
Merge branch 'krepo' into rbfrontend
Ychosen Dec 7, 2018
fe04431
Fix review bidding button
Ychosen Dec 7, 2018
4d9888a
Bug fixed
Ychosen Dec 7, 2018
e7e91ab
Merge remote-tracking branch 'rbfrontend/rbfrontend'
kuangwanjing Dec 8, 2018
17edeba
refactor sign_up_list
kuangwanjing Dec 8, 2018
2fc4d6d
fix code style error
kuangwanjing Dec 11, 2018
9bdc60e
add comment of sign_up_list and remove the unrelated unit test
kuangwanjing Dec 11, 2018
61a3348
fix action's name and order the bidding topic with topic identifier a…
kuangwanjing Dec 17, 2018
3c35641
fix style
kuangwanjing Dec 17, 2018
4628a26
add test for review_bid
kuangwanjing Dec 18, 2018
4221184
fix style
kuangwanjing Dec 18, 2018
24c28d5
refactor some methods
kuangwanjing Dec 18, 2018
603c7d7
fix code format
kuangwanjing Dec 18, 2018
9dd57d2
fix code style
kuangwanjing Dec 18, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions app/assets/javascripts/tablelist.js.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,11 @@ jQuery ->
items: ">*:not(.sort-disabled)"
update: ->
$.post($(this).data('update-url'), $(this).sortable('serialize'))

$("#reviews").sortable
cursor: 'move',
opacity: 0.65,
tolerance: 'pointer'
items: ">*:not(.sort-disabled)"
update: ->
$.post($(this).data('update-url'), $(this).sortable('serialize'))
2,229 changes: 1,135 additions & 1,094 deletions app/assets/javascripts/tree_display.jsx

Large diffs are not rendered by default.

79 changes: 79 additions & 0 deletions app/controllers/review_bids_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
class ReviewBidsController < LotteryController
# intelligently assign reviews to participants
def run_intelligent_assignment
assignment = Assignment.find_by(id: params[:id])
teams = Team.where(parent_id: params[:id]).map(&:id)
participants = AssignmentParticipant.where(parent_id: params[:id])
participant_ranks = []
participants.each do |participant|
ranks = ReviewBid.get_rank_by_participant(participant, teams)
participant_ranks << {pid: participant.id, ranks: ranks}
end
# we have the availability of topics and ranks of users' choices towards submission now.
data = {
users: participant_ranks,
item_size: assignment.max_reviews_per_submission,
user_size: assignment.num_reviews_required
}
url = WEBSERVICE_CONFIG["review_bidding_webservice_url"]
begin
response = RestClient.post url, data.to_json, content_type: :json, accept: :json
bid_result = JSON.parse(response)["info"]
response_mappings = run_intelligent_bid(assignment, teams, participants, bid_result)
create_response_mappings(assignment, response_mappings)
rescue StandardError => err
flash[:error] = err.message
end
# render :json => response_mappings.to_json
assignment.update_attribute(:review_assignment_strategy, 'Auto-Selected')
flash[:success] = 'The intelligent review assignment was successfully completed for ' + assignment.name + '.'
redirect_to controller: 'tree_display', action: 'list'
end

# if some participants don't get assigned reviews from peerlogic,
# randomly assign availables reviews to them
def run_intelligent_bid(assignment, teams, participants, bid_result)
team_assigned_count = {}
participant_count = {}
response_mappings = []
participants.each do |participant|
participant_count[participant.id] = 0
end
teams.each do |team|
team_assigned_count[team] = 0
end
bid_result.each do |participant_bid|
participant_bid["items"].each do |team_id|
# make a record for the bidding result from peerlogic
response_mappings << {pid: participant_bid["pid"], team: team_id}
# counting how many participants are assigned with this team
team_assigned_count[team_id] += 1
# counting how many reviews are assigned to each participant
participant_count[participant_bid["pid"]] += 1
end
end
# if after the bidding, there are some participants who do not have enough available reviews
# assign them with some random teams
participants.each do |participant|
while participant_count[participant.id] < assignment.num_reviews_required
team = teams[rand(teams.count)]
next if team_assigned_count[team] >= assignment.max_reviews_per_submission
team_assigned_count[team] += 1
participant_count[participant.id] += 1
response_mappings << {pid: participant.id, team: team}
end
end
response_mappings
end

# create response_mapping data with the result from intelligent assignment
def create_response_mappings(assignment, response_mappings)
response_mappings.each do |map|
ReviewResponseMap.create(
reviewed_object_id: assignment.id,
reviewer_id: map[:pid],
reviewee_id: map[:team]
)
end
end
end
61 changes: 57 additions & 4 deletions app/controllers/student_review_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def action_allowed?
'Administrator',
'Super-Administrator',
'Student'].include? current_role_name and
((%w[list].include? action_name) ? are_needed_authorizations_present?(params[:id], "submitter") : true)
((%w[list].include? action_name) ? are_needed_authorizations_present?(params[:id], "submitter") : true)
end

def list
Expand All @@ -20,7 +20,7 @@ def list

@review_mappings = ReviewResponseMap.where(reviewer_id: @participant.id)
# if it is an calibrated assignment, change the response_map order in a certain way
@review_mappings = @review_mappings.sort_by {|mapping| mapping.id % 5 } if @assignment.is_calibrated == true
@review_mappings = @review_mappings.sort_by {|mapping| mapping.id % 5} if @assignment.is_calibrated == true
@metareview_mappings = MetareviewResponseMap.where(reviewer_id: @participant.id)
# Calculate the number of reviews that the user has completed so far.

Expand All @@ -33,12 +33,65 @@ def list

@num_reviews_in_progress = @num_reviews_total - @num_reviews_completed
# Calculate the number of metareviews that the user has completed so far.
@num_metareviews_total = @metareview_mappings.size
@num_metareviews_completed = 0
@num_metareviews_total = @metareview_mappings.size
@num_metareviews_completed = 0
@metareview_mappings.each do |map|
@num_metareviews_completed += 1 unless map.response.empty?
end
@num_metareviews_in_progress = @num_metareviews_total - @num_metareviews_completed
@topic_id = SignedUpTeam.topic_id(@assignment.id, @participant.user_id)
topic_list
end

# the method remove_nullteam_topics remove the topics that are not signed up by any teams
def remove_nullteam_topics(old_array)
new_array = []
for j in (0..(old_array.length - 1)) do
@signupteam = SignedUpTeam.where(topic_id = old_array[j].id).first
if (@signupteam != [] and @signupteam.team_id != 0) then
new_array.insert(-1, old_array[j])
end
end
return new_array
end

# this method is used to show a submission list for students to bid when
# the review strategy is "bidding"
def topic_list
# get the participant that's the reviewer for the assignment
@participant = AssignmentParticipant.find(params[:id])
my_bids = ReviewBid.get_bids_by_participant(@participant)
@bids = []
my_teams = TeamsUser.where(user_id: @participant.user_id).map(&:team_id)
# if self-review is not allowed
if !@assignment.is_selfreview_enabled
my_bids.each do |bid|
next if my_teams.include?(bid.team_id)
@bids << bid
end
else
@bids = my_bids
end
end

# set the priority of review
def set_priority
@participant = AssignmentParticipant.find(params[:participant_id])
@assignment = @participant.assignment
assignment_id = @assignment.id
bidding_teams = ReviewBid.where(participant_id: @participant.id).map(&:team_id)
bidding_teams.each do |team|
ReviewBid.where(team_id: team, participant_id: @participant.id).destroy_all
end
# Refresh with new priority
params[:topic].each_with_index do |team_id, index|
bid_existence = ReviewBid.where(team_id: team_id, participant_id: @participant.id)
if bid_existence.empty?
ReviewBid.create(team_id: team_id, participant_id: @participant.id, priority: index + 1)
else
ReviewBid.where(team_id: team_id, participant_id: @participant.id).update_all(priority: index + 1)
end
end
redirect_to action: 'list', assignment_id: assignment_id
end
end
2 changes: 1 addition & 1 deletion app/controllers/teams_users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def create

if team.is_a?(AssignmentTeam)
assignment = Assignment.find(team.parent_id)
if AssignmentParticipant.find_by(user_id: user.id, assignment_id: assignment.id).nil?
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 <a href=\"#{urlAssignmentParticipantList}\">add</a> this user before continuing."
else
Expand Down
5 changes: 3 additions & 2 deletions app/controllers/tree_display_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,10 @@ def assignments_method(node, tmp_object)
"course_id" => node.get_course_id,
"max_team_size" => node.get_max_team_size,
"is_intelligent" => node.get_is_intelligent,
"has_topic" => node.bidding_review?,
"require_quiz" => node.get_require_quiz,
"allow_suggestions" => node.get_allow_suggestions,
"has_topic" => SignUpTopic.where(['assignment_id = ?', node.node_object_id]).first ? true : false
"allow_suggestions" => node.get_allow_suggestions
# "has_topic" => SignUpTopic.where(['assignment_id = ?', node.node_object_id]).first ? true : false
)
end

Expand Down
2 changes: 2 additions & 0 deletions app/helpers/review_bids_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module ReviewBidsHelper
end
13 changes: 13 additions & 0 deletions app/helpers/student_review_helper.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,15 @@
module StudentReviewHelper
# Render topic row for bidding topic review
def get_intelligent_review_row(biditem)
row_html = '<tr id="topic_' + biditem[:team_id].to_s + '" style="background-color:rbg(47, 352, 0)">'

row_html.html_safe
end

def get_topic_bg_color_by_reveiw(topic, max_team_size)
red = (400 * (1 - (Math.tanh(2 * [max_team_size.to_f / ReviewBid.where(topic_id: topic.id).count, 1].min - 1) + 1) / 2)).to_i.to_s
green = (400 * (Math.tanh(2 * [max_team_size.to_f / ReviewBid.where(topic_id: topic.id).count, 1].min - 1) + 1) / 2).to_i.to_s
'rgb(' + red + ',' + green + ',0)'
end

end
7 changes: 6 additions & 1 deletion app/models/assignment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ class Assignment < ActiveRecord::Base
# Review Strategy information.
RS_AUTO_SELECTED = 'Auto-Selected'.freeze
RS_INSTRUCTOR_SELECTED = 'Instructor-Selected'.freeze
REVIEW_STRATEGIES = [RS_AUTO_SELECTED, RS_INSTRUCTOR_SELECTED].freeze
RS_BIDDING = 'Bidding'.freeze
REVIEW_STRATEGIES = [RS_AUTO_SELECTED, RS_INSTRUCTOR_SELECTED, RS_BIDDING].freeze
DEFAULT_MAX_REVIEWERS = 3
DEFAULT_MAX_OUTSTANDING_REVIEWS = 2

Expand Down Expand Up @@ -149,6 +150,10 @@ def dynamic_reviewer_assignment?
end
alias is_using_dynamic_reviewer_assignment? dynamic_reviewer_assignment?

def bidding_review?
self.review_assignment_strategy == RS_BIDDING
end

def scores(questions)
scores = {}
scores[:participants] = {}
Expand Down
4 changes: 4 additions & 0 deletions app/models/assignment_node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ def get_course_id
@assign_node ? @assign_node.course_id : Assignment.find_by(id: self.node_object_id).try(:course_id)
end

def bidding_review?
Assignment.find_by(id: self.node_object_id).try(:bidding_review?)
end

def belongs_to_course?
!get_course_id.nil?
end
Expand Down
6 changes: 4 additions & 2 deletions app/models/assignment_team.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def submitted_files(path = self.path)
# 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 \"" + id.to_s + "\" was not found. <a href='/assignment/new'>Create</a> this assignment?"
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)
Expand All @@ -130,7 +130,9 @@ def copy(course_id)
# 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)
AssignmentParticipant.create(parent_id: assignment_id, user_id: user.id)
# 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

# return a hash of scores that the team has received for the questions
Expand Down
60 changes: 60 additions & 0 deletions app/models/review_bid.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
class ReviewBid < ActiveRecord::Base
attr_accessor :bid_topic_name, :bid_topic_identifier, :bid_team_name
# this method is called when the bidding is run,
# get the ranking of teams ordered by the participant from the bids
def self.get_rank_by_participant(participant, assignment_teams)
bids = ReviewBid.where(participant_id: participant.id).order(:priority)
# if the participant has not selected any team yet, provide a default rank
if bids.empty?
return assignment_teams.shuffle
else
return bids.map(&:team_id)
end
end

# get the bidding list of the participant
def self.get_bids_by_participant(participant)
assignment = participant.assignment
assignment_teams = AssignmentTeam.where(parent_id: assignment.id)
signed_up_teams = []
topics = SignUpTopic.where(assignment_id: assignment.id)
topics.each do |topic|
signed_up_teams += SignedUpTeam.where(topic_id: topic.id, is_waitlisted: 0)
end
bids = ReviewBid.where(participant_id: participant.id).order(:priority)
default = false
# if the participant has not selected any team yet, construct a rank list for views
if bids.empty?
default = true
signed_up_teams.each do |team|
bids << ReviewBid.new(team_id: team.team_id, participant_id: participant.id)
end
end
bids.each do |bid|
self.match_bid_with_team_topic(bid, signed_up_teams, assignment_teams, topics)
end
# if getting a default list, sort by topic_identifier
if default
bids.order(:bid_topic_identifier)
end
bids
end

# for the bid list to show includes topic name, topic identifier and the team name,
# this method is used to wrap up all the information for bid item.
def self.match_bid_with_team_topic(bid, signed_up_teams, assignment_teams, topics)
signed_up_teams.each do |team|
topics.each do |topic|
if bid.team_id == team.team_id && topic.id == team.topic_id
bid.bid_topic_name = topic.topic_name
bid.bid_topic_identifier = topic.topic_identifier
end
end
end
assignment_teams.each do |assignment_team|
if bid.team_id == assignment_team.id
bid.bid_team_name = assignment_team.name
end
end
end
end
10 changes: 8 additions & 2 deletions app/views/assignments/edit/_general.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ function microtaskChanged() {
if (<%= @assignment_form.assignment.max_team_size == 1%>){
team_count_field.attr('hidden', true);
}
}
}
}

function staggeredDeadlineChanged() {
Expand All @@ -266,9 +266,15 @@ function microtaskChanged() {
if (val == 'Auto-Selected') {
jQuery('#assignment_review_topic_threshold_row').removeAttr('hidden');
jQuery('#instructor_selected_review_mapping_mechanism').attr('hidden', true);
} else {
jQuery('#bidding_options').attr('hidden', true)
} else if (val == 'Instructor-Selected') {
jQuery('#instructor_selected_review_mapping_mechanism').removeAttr('hidden');
jQuery('#assignment_review_topic_threshold_row').attr('hidden', true);
jQuery('#bidding_options').attr('hidden', true)
} else {
jQuery('#instructor_selected_review_mapping_mechanism').attr('hidden', true);
jQuery('#assignment_review_topic_threshold_row').attr('hidden', true);
jQuery('#bidding_options').removeAttr('hidden')
}
}

Expand Down