CSC/ECE 517 Fall 2021 - E2165. Fix teammate-review view

From Expertiza_Wiki
Jump to navigation Jump to search

Team Introduction

Dr. Edward Gehringer (instructor), Parimal Mehta (mentor)

1. Anjali Garg (unity ID : agarg25)

2. Arvasu Chikara (unity ID: achikar2)

3. Lalit Bangad (unity ID: llbangad)

4. Vinay Deshmukh (unity ID: vdeshmu)


Problems to be addressed

After a project is submitted and reviewed, both the team members (students) and instructors should be able to view the teammate reviews. Students should be able to view the reviews by going to a particular assignment's -> your scores, at the bottom of the page. An instructor can view the teammate reviews by going to assignments -> assignment -> particular team's submission -> view submission -> assign grades -> view scores.

In the case of the instructor, a single heat grid is shown whereas in the case of a team member, we cant view the scores. In the heat grid, it is not clear if the scores visible are for the reviews that the student has *written* for their teammates or the scores that the student has *received* from their teammates. Additionally, for instructors can choose from a list of the students on the team, but they have no way to tell whether the heat grid shows the reviews done *by* that student or done *for* that student.


Methodology Overview

We have divided our work in the following tasks to address the above problems:

1. Distinguish reviews done for a student by rendering separates heatgrids for each student

2. For instructors, the table will have sections based on the students

3. Include a checkbox on the edit assignment page that enables/disables visibility of heat grid to students

4. Fix the average column for a table (which breaks for some reviews)

5. Show a composite score derived from all reviews. Make this code generalized, so other kinds of heat grid views could use it.

6. Changes should work even in an anonymized view


Motivations

1. Enable users to clearly tell which reviews are done by them, and which reviews are done for them.

2. Code should be generic so it can be used for different types of heat grids as well.


Control Flow to access the concerned screens

1. For instructor (Log in as an instructor):-

Screen after logging in by the instructor

Got to assignments on the Manage menu

Go to assignments

Go to submissions for any assignment

Go to Assign Grade

You will land on the page where the changes are made and can be reviewed

2. Student:- (Instructor impersonating a student)

After landing on the assignments page, select any student from an assignment as shown below

Go to/Select any assignment

Go to your scores

You will land on the page where the changes are made and can be reviewed

Old Status of the application

1. Instructor's view of teammate review: This is the current screen given to the instructor. Here we see that the instructor can not determine who has given which review to which student in a team.

The proposed expanded student display.
The proposed expanded student display.


2. Student's view of teammate review: Currently, a student is not able to see what grades were given by their teammates. We need to implement that functionality so that the student is able to view those grades.

The current instructor view.
The current review display for instructors.


3. Error in existing UI for student: In the existing UI for a student, the highlighted block should not be rendered. This part of the screen is meant for the instructors/TAs.

Existing UI for student
Existing UI for student.


Proposed changes

1) Proposed UI for instructor : The instructor is able to see the score for all teammates. In the existing code, only one teammate's scores are being shown. But, we are planning to change the way the (VmQuestionResponse array is populated)[1], so that the scores for all teammates are appended to it so it correctly shows what should have already been shown in this screen for the instructor.

Proposed UI for instructor
Proposed UI for instructor.


Note: When a student visits this screen, they will only be shown the reviews received by them, assuming the instructor has allowed visibility of teammate reviews to students.


2) Show detailed review for a particular question: The instructor can see detailed review given to a student by their teammates by clicking on a particular question number in the table.

New UI for students
New UI for students


3) Show teammate review to student UI : The existing code doesn't render a student's teammate review scores at all. We are planning to render the scores to a student (as shown below) when they visit this screen. However, the visibility of this screen is contingent on whether the instructor has allowed visibility of the teammate review screen to students. If the instructor has disabled this feature, then the heatgrid will not be displayed at all.

New UI for students
New UI for students


User Stories

1. As an instructor, I can view what reviews one teammate has given to other teammates.

2. As a student, I can view what reviews my teammates have given to me.

3. As an instructor, I can choose if I want to see all student's teammates reviews together or one by one.

4. As a student, if I am also a TA for another subject, I should not be able to see an instructor's view to the view_team page.

5. As an instructor, I should be able to see the name of the reviewers for the reviews to a particular student.

UML diagram for the system


Changes to the models

1. Added new instance variable and method to the class VmQuestionResponseScoreCell. The reviewer_id method is a helper method that retrieves the ID for the reviewer who created this response.


2. Added two new methods to VmQuestionResponse class.The reviewee_id method is a helper method within the VmQuestionReponse model to retrieve the `reviewee_id` for a heatgrid. The reviewee_name method is a helper method within the VmQuestionReponse model to retrieve the name of the student who is being reviewed for a heatgrid. This method uses the functionality to anonymize the name when we are in anonymized view.

Explanation of fixes

The below diagram illustrates the proposed code change for showing all team members with the teammate review data to an instructor. The existing code only iterated on the `Participant` whose ID is present in the URL of the page. To fix this, we will iterate over all teammates of the given participant so that all teammate reviews for all team members are shown to the instructor.

Flow diagram of proposed changes for showing team review data to instructor

Changes made to the code are shown below. Every change is shown and explained separately

A. Added the following to app/assets/stylesheets/grades.scss:

    .teammate_table.hidden {
      display: none;
      }


B. In app/controllers/grades_controller.rb:

B.1:

Old code: The existing code does not differentiate when adding a view model for the TeammateReview questionnaire

 questionnaires.each do |questionnaire|
    ...
    @vmlist << populate_view_model(questionnaire)
 end

New code: Since the current page URL contains an ID for the participant, from which we get an assignment, which gives us a list of questionnaires, we need to iterate over all participants for a `TeammateReveiwQuestionnaire` questionnaire, so that we get the heatgrid view models for all members of a team.

 questionnaires.each do |questionnaire|
    ...
    if questionnaire.type == "TeammateReviewQuestionnaire"
       # as teammate review will be different for each participant
       # we need to create a separate table for each teammate
       @participant.team.participants.each do |team_participant|
         @vmlist << populate_view_model(team_participant, questionnaire, @team)
       end
     else
       # only one participant needs iterating, as apart from teammatereview
       # all other reviews are at "team" level
       @vmlist << populate_view_model(@participant, questionnaire, @team)
     end
  end

B.2:

Old code: This code uses @participant as an instance variable, but this structure will not work as we need to call this ViewModel with multiple participants when iterating for a teammate review questionnaire.

   def populate_view_model(questionnaire)
     vm = VmQuestionResponse.new(questionnaire, @assignment, @round)
     vmquestions = questionnaire.questions
     vm.add_questions(vmquestions)
     vm.add_team_members(@team)
     vm.add_reviews(@participant, @team, @assignment.vary_by_round)
     vm.number_of_comments_greater_than_10_words
     vm 
   end

New code: We need to call this function with a separate participant in each invocation due to our change for TeammateReviewQuestionnaire, hence it makes sense to make `participant` as an argument to this function. Similarly, as `team` is a closely related model to `participant`, we are passing it as an argument, to keep this function as "pure" as possible.

   def populate_view_model(participant, questionnaire, team)
     ...
     vm.add_team_members(team)
     vm.add_reviews(participant, team, @assignment.vary_by_round)
     ...
   end

C. In app/models/vm_question_response.rb:

C.1: Added the reviewee_id method: Created a helper method within the VmQuestionReponse model to retrieve the `reviewee_id` for a heatgrid

   def reviewee_id
     @participant_who_is_reviewee.user_id
   end 

C.2: Added the reviewee_name method: Created a helper method within the VmQuestionReponse model to retrieve the name of the student who is being reviewed for a heatgrid. This method uses the functionality to anonymize the name when we are in anonymized view.

   def reviewee_name(ip_address = nil)
     part = @participant_who_is_reviewee
     User.find_by(id: reviewee_id).fullname(ip_address)
   end

C.3:

Old code: Does not keep track of the student being reviewed

   elsif @questionnaire_type == "TeammateReviewQuestionnaire"
     reviews = participant.teammate_reviews
     reviews.each do |review|
       review_mapping = TeammateReviewResponseMap.find_by(id: review.map_id)
       participant = Participant.find(review_mapping.reviewer_id)

New code: Add an instance variable to keep track of the student who is being reviewed in this VmQuestionReponse

   elsif @questionnaire_type == "TeammateReviewQuestionnaire"
     reviews = participant.teammate_reviews
     @participant_who_is_reviewee = participant
     reviews.each do |review|
       review_mapping = TeammateReviewResponseMap.find_by(id: review.map_id)
       participant = Participant.find(review_mapping.reviewer_id)

C.4:

Old code: Existing VmQuestionReponseScoreCell keeps no information about the reviewer, as it only stores the answer score

   row.score_row.push(VmQuestionResponseScoreCell.new(answer.answer, color_code, answer.comments, vm_tag_prompts))

New code: Keep track of the response ID, so that we can retrieve the user ID for the reviewer in the model to use in the view file.

   row.score_row.push(VmQuestionResponseScoreCell.new(answer.answer, color_code, answer.comments, vm_tag_prompts, answer.response_id))

D. In app/models/vm_question_response_score_cell.rb:

D.1: In the method initialize of class VmQuestionResponseScoreCell

Old code: Existing code doesn't keep track of the response id

   def initialize(score_value, color_code, comments, vmprompts = nil)
     @score_value = score_value
     @color_code = color_code
     @comment = comments
     @vm_prompts = vmprompts
   end

New code: We added an instance variable to keep track of response_id

   def initialize(score_value, color_code, comments, vmprompts = nil, response_id)
     ...
     @response_id = response_id
   end

D.2: Added a method reviewer_id to the class VmQuestionResponseScoreCell: This helper method retrieves the ID for the reviewer who created this response.

   def reviewer_id
     resp = Response.find(@response_id)
     map = ResponseMap.find(resp.map_id)
     map.reviewer_id
   end

E. In app/views/grades/_add_icon_to_name.html.erb:

E.1 Added the following code to the file: Write the code to generate the review text only once to reduce WET code

   <%# Determine the content of the reviewed by text %>
   <% review_text = "" %>
   <% if @current_role_name.eql?'Student' or questionnaire_type != "TeammateReviewQuestionnaire" %>
     <%# Use Review Template:Index format for student view, and when questionnaire_type is not teammatereview %>
     <% review_text = "Review #{i + 1}" %>
   <% else %>
     <% resp = Response.find(review.id) %>
     <% map = ResponseMap.find(resp.map_id) %>
     <% reviewer_id = map.reviewer_id %>
     <% fullname = Participant.find_by(id: reviewer_id).fullname(session[:ip]) %>
     <% review_text = "Review by #{fullname}" %>
   <% end%>

E.2:

Old code: Existing code is very unreadable as it's not indented, and written in a single line with 359 characters, which breaks the ruby style guide rules

   <% if review.done_by_staff_participant? %>

<img data-toggle="tooltip" data-placement="right" title="Icon indicates this review was done by course staff" class="img_icon" src="/assets/staff.png"> <a target="_blank" href="../response/view?id=<%= review.id.to_s %>" data-toggle="tooltip" data-placement="right" title="Click to see details"><%= "Review " + i.to_s  %></a>

   <% else %>

<a target="_blank" href="../response/view?id=<%= review.id.to_s %>" data-toggle="tooltip" data-placement="right" title="Click to see details"><%= "Review " + i.to_s %></a>

   <% end %>

New code: Correctly indented and removes WET repetition of code blocks and makes the resulting code DRYer

   <% if review.done_by_staff_participant? %>
     <img
     data-toggle="tooltip"
     data-placement="right"
     title="Icon indicates this review was done by course staff" class="img_icon"
     src="/assets/staff.png"
     >
   <% end %>
   <a
     target="_blank"
     href="../response/view?id=<%= review.id.to_s %>"
     data-toggle="tooltip"
     data-placement="right"
     title="Click to see details">
     <%= review_text %>
   </a>

F.In app/views/grades/view_team.html.erb:

F.1: Added function teammateSelectOnChange: This function toggles the visibility for the teammate heatgrids when the instructor changes the dropdown

  function teammateSelectOnChange(selectNode) {
      const revieweeIdToShow= selectNode.value;
      const tableSelector = ".teammate_table";
      if(revieweeIdToShow == "all") {
        document.querySelectorAll(tableSelector).forEach((tableNode) => {
          tableNode.classList.remove("hidden");
        });
        return;
      }

    document.querySelectorAll(tableSelector).forEach((tableNode) => {
      const tableRevieweeId = tableNode.getAttribute('data-reviewee_id');
      if(tableRevieweeId == revieweeIdToShow) {
        tableNode.classList.remove("hidden");
      } else {
        tableNode.classList.add("hidden");
      }
      });
    }

F.2:

Old code: Existing code doesn't render TeammateReviewQuestionnaire

    <% @vmlist.each do |vm| %>
        <% if (vm.questionnaire_display_type == "Metareview" or vm.questionnaire_display_type == "Author Feedback" or vm.questionnaire_display_type == "Teammate Review") and @current_role_name.eql?'Student' %>
        <% else %>
        <% if vm.list_of_reviewers.length > 0 %>

New code: Use a boolean to render a dropdown (that contains the students' IDs and names) just before the first TeammateReviewQuestionnaire, and unset that boolean so we only render 1 table.

<%# This boolean is used to render a dropdown just before the first teammate review heat grid %>
<% first_teammate_review_appeared = false %>
<% @vmlist.each do |vm| %>
     <% if (not @current_role_name.eql?'Student' and
        not first_teammate_review_appeared and
        vm.questionnaire_type == "TeammateReviewQuestionnaire" 
      ) %>
      <div>
        <select onchange="teammateSelectOnChange(this)">
          <option value="all">All</option>
          <% @vmlist.select { |tm_vm| tm_vm.questionnaire_type == "TeammateReviewQuestionnaire" }.each do |tm_vm| %>
            <option value="<%= tm_vm.reviewee_id %>">
              <%= tm_vm.reviewee_name(session[:ip]) %>
            </option>
          <% end %>
        </select>
      </div>
      <% first_teammate_review_appeared = true %>
    <% end %>

New code: Add conditions which check for the truthiness of ```@assignment.show_teammate_reviews``` and if the current user is a student to decide whether to render the table or not.  Since the existing code checks truthiness for the cases for which NOT to render a table, we have followed the existing style of the code.

1) If type of view model is teammate review questionnaire, and current role is student, and if @assignment.show_teammate_reviews is false, then we don't render anything
2) If type of view model is teammate review questionnaire, and current role is student, but @assignment.show_teammate_reviews is true, then we don't render anything if the current reviewer_id is different to the currently logged in user.
<pre>
    <% if (vm.questionnaire_display_type == "Metareview" or vm.questionnaire_display_type == "Author Feedback") and @current_role_name.eql?'Student' %>
    <% elsif (
      vm.questionnaire_type == "TeammateReviewQuestionnaire" and
      @current_role_name.eql?'Student' and
      !@assignment.show_teammate_reviews
      ) %>
    <% elsif (
      vm.questionnaire_type == "TeammateReviewQuestionnaire" and 
      @current_role_name.eql?'Student' and
      @assignment.show_teammate_reviews and current_user.id != vm.reviewee_id
      )%>

New code: Finally, add a CSS class and a `data-reviewee_id` to keep track of the heatgrids when toggling visibility.

    <% else %>
        <% if vm.questionnaire_type == "TeammateReviewQuestionnaire" %>
          <div class="teammate_table" data-reviewee_id="<%= vm.reviewee_id %>">
        <% else %>
          <div>
        <% end %>

F.3

Old code: Existing code doesn't print the teammate who is being reviewed.

<!-- display the list of questions for this questionnaire -->
          <h4 style="display:inline-block;"><%= vm.questionnaire_display_type %>
            <% if vm.questionnaire_type == "ReviewQuestionnaire" %>
                (Round: <%= vm.round.to_s  %> of <%= vm.rounds.to_s  %>)
            <%end %>

New code: If TeammteReviewQuestionnaire is used, then use the `:reviewee_name` method for VmQuestionResponse model to print the name of the student who is being reviewed.

<!-- display the list of questions for this questionnaire -->
          <h4 style="display:inline-block;"><%= vm.questionnaire_display_type %>
            <% if vm.questionnaire_type == "ReviewQuestionnaire" %>
                (Round: <%= vm.round.to_s  %> of <%= vm.rounds.to_s  %>)
            <% elsif vm.questionnaire_type == "TeammateReviewQuestionnaire" %>
                (For: <%= vm.reviewee_name(session[:ip]) %>)
            <%end %>

F.4

Old code: Existing code doesn't inform the partial about the type of review being done

<% vm.list_of_reviews.each do |review| %>
              <!-- instructors (or higher level users) see reviewer name, students see anonymized string -->
              <% if (['Student'].include? @current_role_name) && @assignment.is_anonymous%>
                <%= render :partial => 'add_icon_to_name', :locals => {review: review, i: i} %>
              <% else %>
                  <% user_name = User.find(Participant.find(ResponseMap.find(Response.find(review.id).map_id).reviewer_id).user_id).fullname(session[:ip]).to_s %>
                <%= render :partial => 'add_icon_to_name', :locals => {review: review, i: i} %>
              <% end %>
              <% i += 1 %>
          <% end %>
New code: Passes the type of review being done to the partial, so it can decide on how to render the review title. Note: This code seems to use repetition, but since the conditional used here are accessing `Anonymous` assignments and checking for user roles, we have not modified this code, as it is not directly related to our scope.
<pre>
<% vm.list_of_reviews.each do |review| %>
              <!-- instructors (or higher level users) see reviewer name, students see anonymized string -->
              <% if (['Student'].include? @current_role_name) && @assignment.is_anonymous%>
                <%= render :partial => 'add_icon_to_name', :locals => {review: review, i: i, questionnaire_type: vm.questionnaire_type } %>
              <% else %>
                <% user_name = User.find(Participant.find(ResponseMap.find(Response.find(review.id).map_id).reviewer_id).user_id).fullname(session[:ip]).to_s %>
                <%= render :partial => 'add_icon_to_name', :locals => {review: review, i: i, questionnaire_type: vm.questionnaire_type } %>
              <% end %>
              <% i += 1 %>
          <% end %>


Old code: Contains a `metrics-1` column

          <th class="sorter-true" align="right" >
            Avg<span></span>
          </th>
          <th class="sorter-false">
            <span  data-toggle="tooltip" data-placement="right" title="A count of comments, for the respective question, which have word count > 10. The purpose of this metric is to represent how many comments for the question are of a substantial length to provide quality feedback.">metric-1</span>
          </th>
        </tr>
        </thead>


New code: As per discussion with mentor, this column has been removed in our PR.

          <th class="sorter-true" align="right" >
            Avg<span></span>
          </th>
        </tr>
        </thead>

F.5

Old code: It creates the ID inline in multiple places, so if we decide to change the ID generation logic, we need to modify it in multiple places. (which happened when we started rendering multiple tables for each teammate).

<% vm.list_of_rows.each do |row| %>
            <!-- notice the data-target. because we're toggling via looped code, we need to dynamically generate the identifier. -->
            <tr data-toggle="collapse" class="accordion-toggle" data-target="#hid<%= row.question_id.to_s + vm.round.to_s %>">

New code: Generate the unique HTML ID once, in a readable manner once. And conditionally make it more unique, if it is a TeammateReviewQuestionnaire, since jQuery code requires these IDs to be unique amongst different tables. If we don't make it more unique, then clicking on one teammate review table will toggle the table visibility for the first table. To prevent this "clashing", we need to make these IDs more unique for a TeammateReviewQuestionnaire.

<% vm.list_of_rows.each do |row| %>
            <% target_id = "hid__#{row.question_id}__#{vm.round}" %>
            <% if vm.questionnaire_type == "TeammateReviewQuestionnaire" %>
              <%# Since we render different heatgrids with the same questions for each
              teammember, we need to distinguish the ID, based on the user for whom,
              the reviews have been created. %>
              <% target_id += "__#{vm.reviewee_id}" %>
            <% end%>

            <!-- notice the data-target. because we're toggling via looped code, we need to dynamically generate the identifier. -->
            <tr data-toggle="collapse" class="accordion-toggle" data-target="<%= "#" + target_id %>">


Old code: Written on a single line crossing 80 characters on a single line

              <% row.score_row.each do |score| %>
                  <% score_comment = score.comment.nil? ? '' : score.comment %> <!-- changing nil to null string to avoid Null Pointer Exception -->
                  <td class="<%= score.color_code %>" align="center" data-toggle="tooltip" title="<%= strip_tags(score.comment).html_safe%>">
                    <span class="<%=  underlined?(score) %>"><%= score.score_value.to_s %></span>
                  </td>
<% end %>


New code: Make code more readable by indenting it and splitting it over multiple lines

              <!-- actual cells with scores and colored background. -->
              <% row.score_row.each do |score| %>
                  <% score_comment = score.comment.nil? ? '' : score.comment %>
                  <!-- changing nil to null string to avoid Null Pointer Exception -->
                  <td class="<%= score.color_code %>" align="center" data-toggle="tooltip" title="<%= strip_tags(score.comment).html_safe%>">
                    <span class="<%=  underlined?(score) %>">
                      <%= score.score_value.to_s %>
                    </span>
                  </td>
<% end %>

F.6 Removed the following code as it corresponds to the deleted column

 <td  class = 'cf' align="center">
                <%= row.countofcomments.to_s %>
              </td>

F.7

Old code: Existing code just unconditionally prints the text "Review" followed by an index.

                    <% for index in 0 .. row.score_row.length - 1 %>
                        <tr>
                          <td>Review <%=index + 1%></td>

New code: Conditionally change the review text that is displayed based on the type of review and the access level of the user

                    <% for index in 0 .. row.score_row.length - 1 %>
                        <tr>
                          <% if @current_role_name.eql?'Student' or vm.questionnaire_type != "TeammateReviewQuestionnaire" %>
                            <%# Use Review {{index}} format for student view, and
                            when questionnaire_type is not teammatereview %>
                            <td>Review <%=index + 1%></td>
                          <% else %>
                            <td>
                              <% reviewer_id = row.score_row[index].reviewer_id %>
                              <% fullname = Participant.find_by(id: reviewer_id).fullname(session[:ip]) %>
                              Review by <%= fullname %>
                            </td>
                          <% end%>

G. In spec/controllers/grades_controller_spec.rb:

G.1

Old code

before(:each) do
  allow(AssignmentParticipant).to receive(:find).with('1').and_return(participant)
  allow(participant).to receive(:team).and_return(team)
  stub_current_user(instructor, instructor.role.name, instructor.role)
  allow(Assignment).to receive(:find).with('1').and_return(assignment)
@@ -91,29 +90,106 @@
  end
end

New code

before(:each) do
  allow(participant).to receive(:team).and_return(team)
  stub_current_user(instructor, instructor.role.name, instructor.role)
  allow(Assignment).to receive(:find).with('1').and_return(assignment)
@@ -91,29 +90,106 @@
  end
end


G.2 Remove xdescribe '#view_team' and change describe '#view_team' as follows.

Old code

describe '#view_team' do
render_views
context 'when view_team page is viewed by a student who is also a TA for another course' do
  it 'renders grades#view_team page' do
    allow(participant).to receive(:team).and_return(team)
    allow(AssignmentQuestionnaire).to receive(:find_by).with(assignment_id: 1, questionnaire_id: 1).and_return(assignment_questionnaire)
    allow(AssignmentQuestionnaire).to receive(:where).with(any_args).and_return([assignment_questionnaire])
    allow(assignment).to receive(:late_policy_id).and_return(false)
    allow(assignment).to receive(:calculate_penalty).and_return(false)
    allow(assignment).to receive(:compute_total_score).with(any_args).and_return(100)
    allow(review_questionnaire).to receive(:get_assessments_round_for).with(participant, 1).and_return([review_response])
    allow(Answer).to receive(:compute_scores).with([review_response], [question]).and_return(max: 95, min: 88, avg: 90)
    params = {id: 1}
    allow(TaMapping).to receive(:exists?).with(ta_id: 1, course_id: 1).and_return(true)

New code

describe '#view_team' do
render_views

# All stubs and factories are instantiated within this scope so as to not 
# clash with other broken test.
# The names of the factories created for testing view_team
# have `_vt` suffix ```where necessary``` to differentiate them from 
# the above declared factories

let(:student1_vt) { create(:student, name: "barry", id: 12) }
let(:student2_vt) { create(:student, name: "iris", id: 13) }

let(:tm_questionnaire) {
  build(
    :teammate_review_questionnaire,
    id: 12,
    questions: [question],
    max_question_score: 5
  )
}
let(:assignment_vt) {
  create(:assignment, id: 6, max_team_size: 2,
  questionnaires: [tm_questionnaire])
}
let(:assignment_questionnaire_vt) {
  create(:tm_assignment_questionnaire, id: 12, used_in_round: nil,
  assignment: assignment_vt, questionnaire: tm_questionnaire) }
let(:team_vt) { create(:assignment_team, id: 12,
assignment: assignment_vt) }
let(:participant_vt) { create(:participant, id: 12,
  assignment: assignment_vt, user_id: student1_vt.id) }
let(:participant2_vt) { create(:participant, id: 13,
  assignment: assignment_vt, user_id: student2_vt.id) }

before(:each) do
  # Need to stub this method so the factory instance with
  # the stubbed :team method is returned by :find
  # instead of a separate instance with the same data
  allow(AssignmentParticipant)
    .to receive(:find)
    .with(participant_vt.id.to_s)
    .and_return(participant_vt)
  allow(participant_vt).to receive(:team).and_return(team_vt)
  allow(team_vt).to receive(:participants).and_return([participant_vt, participant2_vt])
  allow(AssignmentQuestionnaire)
    .to receive(:find_by)
    .with(assignment_id: assignment_vt.id, questionnaire_id: tm_questionnaire.id)
    .and_return(assignment_questionnaire_vt)
  allow(AssignmentQuestionnaire)
    .to receive(:find_by)
    .with(assignment_id: assignment.id, questionnaire_id: review_questionnaire.id)
    .and_return(assignment_questionnaire_vt)
  allow(AssignmentQuestionnaire)
    .to receive(:where)
    .with(any_args)
    .and_return([assignment_questionnaire_vt])
  allow(assignment_vt)
    .to receive(:late_policy_id)
    .and_return(false)
  allow(assignment_vt)
    .to receive(:calculate_penalty)
    .and_return(false)
  allow(assignment_vt)
    .to receive(:compute_total_score)
    .with(any_args)
    .and_return(100)
  allow(review_questionnaire).to receive(:get_assessments_round_for).with(participant, 1).and_return([review_response])
  allow(Answer).to receive(:compute_scores).with([review_response], [question]).and_return(max: 95, min: 88, avg: 90)
end

 it 'renders grades#view_team page' do
  params = {id: participant_vt.id}
  get :view_team, params
  expect(response).to render_template(:view_team)
end

context 'when view_team page is opened by student' do
  it 'dropdown is not rendered' do
    session = { user: student1_vt }
    params = {id: participant_vt.id}
    stub_current_user(student1_vt, student1_vt.role.name, student1_vt.role)
    get :view_team, params
    expect(response.body).to_not have_selector('select')
  end
end

context 'when view_team page is opened by instructor' do
  it 'dropdown is rendered' do
    session = { user: instructor }
    params = {id: participant_vt.id}
    get :view_team, params
    expect(response.body).to have_selector('select')
  end
end

context 'when view_team page is viewed by a student who is also a TA for another course' do
  it 'renders grades#view_team page' do
    params = { id: participant_vt.id }
    allow(TaMapping).to receive(:exists?).with(ta_id: participant_vt.user_id, course_id: 1).and_return(true)


H. In spec/factories/factories.rb:

H.1 Add the following factory:

factory :teammate_review_questionnaire, class: TeammateReviewQuestionnaire do

H.2 Add another factory

factory :tm_assignment_questionnaire, class: AssignmentQuestionnaire do
  user_id 1
  questionnaire { association(:teammate_review_questionnaire) }
  questionnaire_weight 100
  used_in_round nil
  topic_id nil
  dropdown 1
end

Testing

The tests written as a part of this project can be run by running the following command on terminal:

1. rspec ./spec/controllers/grades_controller_spec.rb -e "#view_team"

2. rspec .vm_question_response_score_cell_spec.rb -e "#reviewer_id"


OLD STATUS

1. Earlier, the tests contained an xdescribe test to check if the view_team page is rendered correctly. However, this test was being skipped as this was an xdescribe segment. There was another test for when the view_team page is accessed by a student who is also a TA for another course. This can be checked here.

 xdescribe '#view_team' do
    it 'renders grades#view_team page' do
      allow(participant).to receive(:team).and_return(team)
      params = {id: 1}
      get :view_team, params
      expect(response).to render_template(:view_team)
    end
  end

  describe '#view_team' do
    render_views
    context 'when view_team page is viewed by a student who is also a TA for another course' do
      it 'renders grades#view_team page' do
        allow(participant).to receive(:team).and_return(team)
        allow(AssignmentQuestionnaire).to receive(:find_by).with(assignment_id: 1, questionnaire_id: 1).and_return(assignment_questionnaire)
        allow(AssignmentQuestionnaire).to receive(:where).with(any_args).and_return([assignment_questionnaire])
        allow(assignment).to receive(:late_policy_id).and_return(false)
        allow(assignment).to receive(:calculate_penalty).and_return(false)
        allow(assignment).to receive(:compute_total_score).with(any_args).and_return(100)
        allow(review_questionnaire).to receive(:get_assessments_round_for).with(participant, 1).and_return([review_response])
        allow(Answer).to receive(:compute_scores).with([review_response], [question]).and_return(max: 95, min: 88, avg: 90)
        params = {id: 1}
        allow(TaMapping).to receive(:exists?).with(ta_id: 1, course_id: 1).and_return(true)
        stub_current_user(ta, ta.role.name, ta.role)
        get :view_team, params
        expect(response.body).not_to have_content "TA"
      end
    end
  end


NEW STATUS

1. The view_team page is rendered correctly for a given student or an instructor. This can be checked in this file.

    it 'renders grades#view_team page' do
      params = {id: participant_vt.id}
      get :view_team, params
      expect(response).to render_template(:view_team)
    end

2. When students access then the view_team page, the dropdown to select a student's teammate reviews is not rendered on the page. The dropdown is rendered if the page is accessed by an instructor. This can be checked in this file.

   context 'dropdown for selecting a teammate review' do
    render_views
      it 'is not rendered is not rendered when page is opened by student' do
        session = { user: student1_vt }
        params = {id: participant_vt.id}
        stub_current_user(student1_vt, student1_vt.role.name, student1_vt.role)
        get :view_team, params
        expect(response.body).to_not have_selector('select')
      end

      it 'is rendered when page is opened by instructor' do
        session = { user: instructor }
        params = {id: participant_vt.id}
        get :view_team, params
        expect(response.body).to have_selector('select')
      end
    end


3. When a student who is also a TA for another course access the view_team page, that student should be able to a student's view to the page. This can be checked in this file.

    context 'when view_team page is viewed by a student who is also a TA for another course' do
      it 'renders grades#view_team page' do
        params = { id: participant_vt.id }
        allow(TaMapping).to receive(:exists?).with(ta_id: participant_vt.user_id, course_id: 1).and_return(true)
        stub_current_user(ta, ta.role.name, ta.role)
        get :view_team, params
        expect(response.body).not_to have_content "TA"
      end
    end

4. If show_teammate_reviews boolean is set to False, then the student should not be able to see the teammate reviews given to him/her. An instructor can still see the teammate reviews. This can be checked in this file.

    context 'when assignment(show reviews) value is unchecked' do
      before(:each) do
        allow(assignment_vt).to receive(:show_teammate_reviews).and_return(false) 
      end
      it 'Student cannot view his score' do
        session = { user: student1_vt }
        params = {id: participant_vt.id}
        stub_current_user(student1_vt, student1_vt.role.name, student1_vt.role)
        get :view_team, params
        expect(response.body).to_not have_selector(".teammate_table[data-reviewee_id=\"#{participant_vt.id}\"]")
        expect(response.body).to_not have_selector(".teammate_table[data-reviewee_id=\"#{participant2_vt.id}\"]")
      end

      it 'Instructor can view the student score' do        
        params = {id: participant_vt.id}        
        get :view_team, params
        expect(response.body).to have_selector(".teammate_table[data-reviewee_id=\"#{participant_vt.id}\"]")
        expect(response.body).to have_selector(".teammate_table[data-reviewee_id=\"#{participant2_vt.id}\"]")
      end
    end
  end

5. If show_teammate_reviews boolean is set to True, then both the student should and instructor should be able to see the teammate reviews. The student should see only the reviews received by him/her. The instructor should be able to see all student's teammate reviews. This can be checked in this file.


    context 'when assignment(show reviews) value is checked' do
      it 'Student can view his score' do
        session = { user: student1_vt }
        params = {id: participant_vt.id}
        stub_current_user(student1_vt, student1_vt.role.name, student1_vt.role)
        get :view_team, params
        expect(response.body).to have_selector(".teammate_table[data-reviewee_id=\"#{participant_vt.id}\"]")
        expect(response.body).to_not have_selector(".teammate_table[data-reviewee_id=\"#{participant2_vt.id}\"]")
      end

      it 'Instructor can view the student score' do        
        params = {id: participant_vt.id}        
        get :view_team, params
        expect(response.body).to have_selector(".teammate_table[data-reviewee_id=\"#{participant_vt.id}\"]")
        expect(response.body).to have_selector(".teammate_table[data-reviewee_id=\"#{participant2_vt.id}\"]")
      end
    end

6. The reviewer id is tracked correctly to show the correct reviewer name in the teammate reviews table. This can be checked in this file

  	describe '#reviewer_id' do
  		it 'returns reviewer id' do
			allow(ResponseMap).to receive(:find).with(1).and_return(review_response_map)
			allow(response).to receive(:populate_new_response).with(:review_response_map, "0").and_return(response)
        	expect(response.id).to eq(1)	
		end
	end

Summary of file changes

The following files are modified:

1. app/controllers/grades_controller.rb - Controller file which changed how the view model is populated

2. app/views/grades/view_team.html.erb - View file, which conditionally rendered the teammate name, for a teammate review heatgrid

3. spec/controllers/grades_controller_spec.rb - For tests verifying the proposed changes

4. app/models/vm_question_response.rb - To modify the view-model so it can tell us the name of the person being reviewed for the current heatgrid

5. app/models/vm_question_response_score_cell.rb -

6. app/assets/stylesheets/grades.scss -

7. app/views/grades/_add_icon_to_name.html.erb -

8. spec/factories/factories.rb -


Design Patterns

Existing code does not use any design patterns, apart from if-else. It uses if-else conditional statements to check what type of questionnaire is present in the viewmodel `VmQuestionResponse`, and based on that, it renders the appropriate content.

We decided to continue with this approach since to change the present coding style, there were required many changes (refactoring) to many files which were out of the scope of the present project. To complete the project in the assigned time, we followed the if-else approach.


References

https://expertiza.ncsu.edu/