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

E1875: Revision planning tool #1302

Merged
merged 34 commits into from
Feb 24, 2019
Merged
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
c762604
Include submission id in questionnaire
rahuliyer95 Nov 28, 2018
c90f3b6
Merge branch 'master' of github.com:expertiza/expertiza into feature/…
rahuliyer95 Nov 28, 2018
4a97d51
Fix: failed migration
rahuliyer95 Nov 29, 2018
73b95cf
Added RevisionReviewQuestionnaire model
rahuliyer95 Nov 29, 2018
13c0154
Allow user to submit RevisionReviewQuestionnaire after 1st round
rahuliyer95 Nov 29, 2018
3f4c043
Fix: CodeClimate issues
rahuliyer95 Nov 29, 2018
e19d8e4
More code-climate fixes
rahuliyer95 Nov 30, 2018
c07a686
Minor bug fixes
rahuliyer95 Nov 30, 2018
c441b9e
Show RevisionReviewQuestionnaire when performing reviews
rahuliyer95 Nov 30, 2018
eac1256
Merge branch 'master' of github.com:expertiza/expertiza into feature/…
rahuliyer95 Nov 30, 2018
de4c5c7
Show RevisionReviewQuestionnaire responses in grades view
rahuliyer95 Dec 1, 2018
c9bbc7c
Include RevisionReviewQuestionnaire while computing total scores
rahuliyer95 Dec 1, 2018
d4d4f39
Include RevisionReviewQuestionnaire in alternate grades view total
rahuliyer95 Dec 1, 2018
461760d
Fixes for Travis CI and Code Climate
rahuliyer95 Dec 1, 2018
07e1543
More Code Climate fixes
rahuliyer95 Dec 1, 2018
1f4a42a
Fix: undefined local variable participant
rahuliyer95 Dec 1, 2018
04eec8c
Fix: Code Climate issues
rahuliyer95 Dec 2, 2018
d503f7c
Added test coverage for changes made to questionaire_controller.rb
BarrettJB Dec 2, 2018
0058c3b
Wrote some cappybara tests, still need more work to be complete
BarrettJB Dec 2, 2018
f8dba15
Bug fixes
rahuliyer95 Dec 3, 2018
2dbc717
Merge branch 'feature/e1875' of github.com:rahuliyer95/expertiza into…
rahuliyer95 Dec 3, 2018
4447409
Fix: question numbers repeat when questionnaires change
rahuliyer95 Dec 4, 2018
c755d14
unit test revision review questionnaire tested to 100%
Dec 5, 2018
e391141
grades controller test need help
Dec 5, 2018
a53b01d
allow deletion of question
amoghtewari Dec 5, 2018
d3cd0de
Code Climate fixes for model spec
rahuliyer95 Dec 7, 2018
ff2fa56
Fix: failing questionnaires controller spec
rahuliyer95 Dec 7, 2018
16e56b9
Code Climate fixes
rahuliyer95 Dec 7, 2018
c97c145
Minor code climate fixes
rahuliyer95 Dec 7, 2018
6181018
Fixing CI Issues before turning the project in
BarrettJB Dec 7, 2018
430f38d
Minor Code Climate fixes
rahuliyer95 Dec 8, 2018
415eb3e
Reordered comments
BarrettJB Dec 10, 2018
177005a
Improved the relationship between Questionnaire and SubmissionRecord
rahuliyer95 Dec 12, 2018
5bfd3de
Improved doc for methods
rahuliyer95 Dec 12, 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
28 changes: 27 additions & 1 deletion app/controllers/grades_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def view_my_scores
@questions = {} # A hash containing all the questions in all the questionnaires used in this assignment
questionnaires = @assignment.questionnaires
retrieve_questions questionnaires
setup_revision_review_questionnaire
# @pscore has the newest versions of response for each response map, and only one for each response map (unless it is vary rubric by round)
@pscore = @participant.scores(@questions)
make_chart
Expand All @@ -101,6 +102,7 @@ def view_team
@questions = {}
questionnaires = @assignment.questionnaires
retrieve_questions questionnaires
setup_revision_review_questionnaire
@pscore = @participant.scores(@questions)
@vmlist = []

Expand All @@ -109,7 +111,8 @@ def view_team
counter_for_same_rubric = 0
questionnaires.each do |questionnaire|
@round = nil
if @assignment.varying_rubrics_by_round? && questionnaire.type == "ReviewQuestionnaire"
is_review_questionnaire = questionnaire.type == "ReviewQuestionnaire"
if @assignment.varying_rubrics_by_round? && is_review_questionnaire
questionnaires = AssignmentQuestionnaire.where(assignment_id: @assignment.id, questionnaire_id: questionnaire.id)
if questionnaires.count > 1
@round = questionnaires[counter_for_same_rubric].used_in_round
Expand All @@ -121,6 +124,7 @@ def view_team
end
vm = VmQuestionResponse.new(questionnaire, @assignment, @round)
vmquestions = questionnaire.questions
vmquestions += revision_review_questions(@round) if is_review_questionnaire
vm.add_questions(vmquestions)
vm.add_team_members(@team)
vm.add_reviews(@participant, @team, @assignment.varying_rubrics_by_round?)
Expand Down Expand Up @@ -323,4 +327,26 @@ def check_self_review_status
def mean(array)
array.inject(0) {|sum, x| sum += x } / array.size.to_f
end

def setup_revision_review_questionnaire
submission_records = SubmissionRecord.where(team_id: @participant.team.id,
assignment_id: @assignment.id,
operation: "Revision Review")
submission_records.each do |record|
key = "review#{record.content}".to_sym
questions = RevisionReviewQuestionnaire.where(submission_record_id: record.id).flat_map(&:questions)
@questions[key] += questions if @questions.key?(key)
@questions[key] = questions unless @questions.key?(key)
end
end

# return the questions in the RevisionReviewQuestionnaire for the given round
# an empty array is returned if nothing is present
def revision_review_questions(round)
submission_records = SubmissionRecord.where(team_id: @participant.team.id,
assignment_id: @assignment.id,
content: round.to_s,
operation: "Revision Review")
submission_records.flat_map {|record| RevisionReviewQuestionnaire.where(submission_record_id: record.id).flat_map(&:questions) }
end
end
56 changes: 49 additions & 7 deletions app/controllers/questionnaires_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@ class QuestionnairesController < ApplicationController

def action_allowed?
if action_name == "edit"
@questionnaire = Questionnaire.find(params[:id])
(['Super-Administrator',
'Administrator'].include? current_role_name) ||
((['Instructor'].include? current_role_name) && current_user_id?(@questionnaire.try(:instructor_id)))

edit_action_allowed?
elsif action_name == "delete"
delete_action_allowed?
else
['Super-Administrator',
'Administrator',
'Instructor',
'Teaching Assistant', 'Student'].include? current_role_name
'Teaching Assistant',
'Student'].include? current_role_name
end
end

Expand Down Expand Up @@ -73,6 +72,8 @@ def create
end
@questionnaire.display_type = display_type
@questionnaire.instruction_loc = Questionnaire::DEFAULT_QUESTIONNAIRE_URL
# save submission record id
@questionnaire.submission_record_id = params[:questionnaire][:submission_record_id]
@questionnaire.save
# Create node
tree_folder = TreeFolder.where(['name like ?', @questionnaire.display_type]).first
Expand Down Expand Up @@ -496,14 +497,55 @@ def save_choices(questionnaire_id)

def questionnaire_params
params.require(:questionnaire).permit(:name, :instructor_id, :private, :min_question_score,
:max_question_score, :type, :display_type, :instruction_loc)
:max_question_score, :type, :display_type, :instruction_loc, :submission_record_id)
end

def question_params
params.require(:question).permit(:txt, :weight, :questionnaire_id, :seq, :type, :size,
:alternatives, :break_before, :max_label, :min_label)
end

# determines if "edit" action is allowed.
# If the Questionnaire with the provided `id` has a SubmissionRecord then check if the
# questionnaire is uploaded by the current user using the `questionnaire_by_user?` method.
# Otherwise, allow the user to edit if he/she is an admin or an instructor for the provided questionnaire's assignment
def edit_action_allowed?
@questionnaire = Questionnaire.find(params[:id])
if @questionnaire.nil? || @questionnaire.submission_record.nil?
admin? || instructor_for_questionnaire?(@questionnaire)
else
questionnaire_by_user?(@questionnaire)
end
end

# determines if "delete" action is allowed.
# If the Questionnaire with the provided `id` has a SubmissionRecord then check if the
# questionnaire is uploaded by the current user using the `questionnaire_by_user?` method.
# Otherwise, allow the user to perform the action
def delete_action_allowed?
@questionnaire = Questionnaire.find(params[:id])
if @questionnaire.nil? || @questionnaire.submission_record.nil?
true
else
questionnaire_by_user?(@questionnaire)
end
end

# check if the current_role_name is of an admin
def admin?
['Super-Administrator', 'Administrator'].include? current_role_name
end

# check if the the current_user is an Instructor for the given questionnaire
def instructor_for_questionnaire?(questionnaire)
current_role_name == 'Instructor' && current_user_id?(questionnaire.try(:instructor_id))
end

# checks if the provided questionnaire is created by the current user
def questionnaire_by_user?(questionnaire)
AssignmentTeam.find(questionnaire.submission_record.team_id).participants.any? {|p| p.user_id == current_user.id }
end

# FIXME: These private methods belong in the Questionnaire model

def export
Expand Down
28 changes: 25 additions & 3 deletions app/controllers/response_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ def edit
@questions.each do |question|
@review_scores << Answer.where(response_id: @response.response_id, question_id: question.id).first
end
@questionnaire = set_questionnaire
# @questionnaire = set_questionnaire
# handled inside set_content
render action: 'response'
end

Expand All @@ -86,6 +87,7 @@ def update
@response.update_attribute('additional_comment', params[:review][:comments])
@questionnaire = set_questionnaire
questions = sort_questions(@questionnaire.questions)
questions += sort_questions(@revision_review_questionnaire.questions) unless @revision_review_questionnaire.nil?
create_answers(params, questions) unless params[:responses].nil? # for some rubrics, there might be no questions but only file submission (Dr. Ayala's rubric)
@response.update_attribute('is_submitted', true) if params['isSubmit'] && params['isSubmit'] == 'Yes'
@response.notify_instructor_on_difference if (@map.is_a? ReviewResponseMap) && @response.is_submitted && @response.significant_difference?
Expand All @@ -107,8 +109,8 @@ def new
# it's unlikely that the response exists, but in case the user refreshes the browser it might have been created.
@response = Response.where(map_id: @map.id, round: @current_round.to_i).first
@response = Response.create(map_id: @map.id, additional_comment: '', round: @current_round, is_submitted: 0) if @response.nil?
questions = sort_questions(@questionnaire.questions)
init_answers(questions)
# questions = sort_questions(@questionnaire.questions) # @questions is initialized inside `set_content` method
init_answers(@questions)
render action: 'response'
end

Expand Down Expand Up @@ -161,7 +163,9 @@ def create

# ,:version_num=>@version)
# Change the order for displaying questions for editing response views.
set_revision_review_questionnaire
questions = sort_questions(@questionnaire.questions)
questions += sort_questions(@revision_review_questionnaire.questions) unless @revision_review_questionnaire.nil?
create_answers(params, questions) if params[:responses]
msg = "Your response was successfully saved."
error_msg = ""
Expand Down Expand Up @@ -281,6 +285,7 @@ def set_content(new_response = false)
@questions = sort_questions(@questionnaire.questions)
@min = @questionnaire.min_question_score
@max = @questionnaire.max_question_score
set_revision_review_content
end

# assigning the instance variables for Edit and New actions
Expand Down Expand Up @@ -317,6 +322,7 @@ def set_questionnaire_for_new_response
"GlobalSurveyResponseMap"
@questionnaire = @map.questionnaire
end
set_revision_review_questionnaire
end

def scores
Expand All @@ -332,10 +338,26 @@ def scores
def set_questionnaire
# if user is not filling a new rubric, the @response object should be available.
# we can find the questionnaire from the question_id in answers
set_revision_review_questionnaire
answer = @response.scores.first
@questionnaire = @response.questionnaire_by_answer(answer)
end

def set_revision_review_questionnaire
round = @response.nil? ? @current_round.to_s : @response.round.to_s
submission_record = SubmissionRecord.find_by(assignment_id: @map.contributor.assignment.id, team_id: @map.contributor.id,
operation: 'Revision Review', content: round)
@revision_review_questionnaire = nil
@revision_review_questionnaire = Questionnaire.find_by(submission_record_id: submission_record.id) unless submission_record.nil?
end

def set_revision_review_content
return if @revision_review_questionnaire.nil?
@min = [@min, @revision_review_questionnaire.min_question_score].min
@max = [@max, @revision_review_questionnaire.max_question_score].max
@questions += sort_questions(@revision_review_questionnaire.questions)
end

def set_dropdown_or_scale
use_dropdown = AssignmentQuestionnaire.where(assignment_id: @assignment.try(:id),
questionnaire_id: @questionnaire.try(:id))
Expand Down
46 changes: 46 additions & 0 deletions app/controllers/submitted_content_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ def action_allowed?
# The view have already tested that @assignment.submission_allowed(topic_id) is true,
# so @can_submit should be true
def edit
# NOTE: The ABC size of the function was 18.55 before we made the edits.
# We don't understand the implications of modifying the existing function as this is one of the
# critical features and hence are leaving it untouched
@participant = AssignmentParticipant.find(params[:id])
return unless current_user_id?(@participant.user_id)
@assignment = @participant.assignment
Expand All @@ -21,6 +24,8 @@ def edit
# @can_submit is the flag indicating if the user can submit or not in current stage
@can_submit = !params.key?(:view)
@stage = @assignment.get_current_stage(SignedUpTeam.topic_id(@participant.parent_id, @participant.user_id))
# RevisionReview submission record
setup_revision_review_edit_view
end

# view is called when @assignment.submission_allowed(topic_id) is false
Expand Down Expand Up @@ -155,6 +160,14 @@ def download
end
end

def begin_planning
participant = AssignmentParticipant.find(params[:id])
return unless current_user_id?(participant.user_id)

record = create_revision_review_submission_record(participant)
redirect_to new_questionnaire_path(model: "RevisionReviewQuestionnaire", private: 0, submission_record_id: record.id)
end

private

def get_file_type file_name
Expand Down Expand Up @@ -236,4 +249,37 @@ def one_team_can_submit_work?
# check one assignment has topics or not
(!@topics.empty? and !SignedUpTeam.topic_id(@participant.parent_id, @participant.user_id).nil?) or @topics.empty?
end

# setup the data necessary for RevisionReview submission
def setup_revision_review_edit_view
# Find the round of the current assignment
@round = @participant.assignment.number_of_current_round(SignedUpTeam.topic_id(@participant.parent_id, @participant.user_id))
@record = revision_review_submission_record_for_round(@participant, @round)
@questionnaire = questionnaire_for_submission(@record)
# Partially created submission record without a corresponding questionnaire should be deleted
@record.delete if @record && @questionnaire.nil?
@record = nil if @record && @questionnaire.nil?
end

# create a RevisionReview submission record for the current round
def create_revision_review_submission_record(participant)
SubmissionRecord.create(team_id: participant.team.id,
user: participant.name,
assignment_id: participant.assignment.id,
content: participant.assignment.number_of_current_round(SignedUpTeam.topic_id(participant.parent_id, participant.user_id)).to_s,
operation: "Revision Review")
end

# returns a RevisionReview submission record for the round
def revision_review_submission_record_for_round(participant, round)
SubmissionRecord.find_by(team_id: participant.team.id,
assignment_id: participant.assignment.id,
operation: "Revision Review",
content: round.to_s)
end

# returns a RevisionReviewQuestionnaire for a given SubmissionRecord if any
def questionnaire_for_submission(submission_record)
Questionnaire.find_by(submission_record_id: submission_record.id, type: "RevisionReviewQuestionnaire") unless submission_record.nil?
end
end
32 changes: 30 additions & 2 deletions app/models/answer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,10 @@ def self.get_total_score(params)
sum_of_weights = 0
max_question_score = 0

@questionnaire = Questionnaire.find(@questions[0].questionnaire_id)
# rahuliyer95: The functionality is the same, only change is that the questions may also contain
# RevisionReviewQuestionnaire's questions submitted by the user. So need to compute that as well
questionnaireData = self.get_questionnaire_data(@questions, @response.id)

questionnaireData = ScoreView.find_by_sql ["SELECT q1_max_question_score ,SUM(question_weight) as sum_of_weights,SUM(question_weight * s_score) as weighted_score FROM score_views WHERE type in('Criterion', 'Scale') AND q1_id = ? AND s_response_id = ?", @questions[0].questionnaire_id, @response.id]
# zhewei: we should check whether weighted_score is nil,
# which means student did not assign any score before save the peer review.
# If we do not check here, to_f method will convert nil to 0, at that time, we cannot figure out the reason behind 0 point,
Expand Down Expand Up @@ -129,4 +130,31 @@ def get_reviewee_from_answer(answer)
map.reviewee_id
end
# end added by ferry for answer tagging

# returns scores computed from ScoreView used in computing `total_score`
def self.get_questionnaire_data(questions, response_id)
questionnaire_ids = questions.map(&:questionnaire_id).uniq
score_views = questionnaire_ids.flat_map do |id|
ScoreView.find_by_sql ["
SELECT q1_max_question_score, SUM(question_weight) AS sum_of_weights, SUM(question_weight * s_score) AS weighted_score
FROM score_views
WHERE type in('Criterion', 'Scale')
AND q1_id = ?
AND s_response_id = ?", id, response_id]
end
[self.get_score_view(score_views)]
end

# Aggregate multiple ScoreView into a single ScoreView
def self.get_score_view(score_views)
score_view = score_views[0]
sum_of_weights = score_views.map(&:sum_of_weights)
score_view.sum_of_weights = sum_of_weights.include?(nil) ? nil : sum_of_weights.inject(0, :+)

weighted_scores = score_views.map(&:weighted_score)
score_view.weighted_score = weighted_scores.include?(nil) ? nil : weighted_scores.inject(0, :+)

score_view.q1_max_question_score = score_views.map(&:q1_max_question_score).max
score_view
end
end
19 changes: 19 additions & 0 deletions app/models/questionnaire.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class Questionnaire < ActiveRecord::Base
has_many :assignment_questionnaires, dependent: :destroy
has_many :assignments, through: :assignment_questionnaires
has_one :questionnaire_node, foreign_key: 'node_object_id', dependent: :destroy
has_one :submission_record, dependent: :destroy

validate :validate_questionnaire
validates :name, presence: true
Expand All @@ -14,6 +15,24 @@ class Questionnaire < ActiveRecord::Base
DEFAULT_MIN_QUESTION_SCORE = 0 # The lowest score that a reviewer can assign to any questionnaire question
DEFAULT_MAX_QUESTION_SCORE = 5 # The highest score that a reviewer can assign to any questionnaire question
DEFAULT_QUESTIONNAIRE_URL = "http://www.courses.ncsu.edu/csc517".freeze
QUESTIONNAIRE_TYPES = ['ReviewQuestionnaire',
'MetareviewQuestionnaire',
'Author FeedbackQuestionnaire',
'AuthorFeedbackQuestionnaire',
'Teammate ReviewQuestionnaire',
'TeammateReviewQuestionnaire',
'SurveyQuestionnaire',
'AssignmentSurveyQuestionnaire',
'Assignment SurveyQuestionnaire',
'Global SurveyQuestionnaire',
'GlobalSurveyQuestionnaire',
'Course SurveyQuestionnaire',
'CourseSurveyQuestionnaire',
'BookmarkratingQuestionnaire',
'QuizQuestionnaire',
'RevisionReviewQuestionnaire'].freeze
# zhewei: for some historical reasons, some question types have white space, others are not
# need fix them in the future.
has_paper_trail

def get_weighted_score(assignment, scores)
Expand Down