CSC/ECE 517 Fall 2024 - E2454. Refactor student task.rb

From Expertiza_Wiki
Revision as of 03:59, 30 October 2024 by Akoul2 (talk | contribs) (→‎Login Details)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Expertiza

Expertiza is a web application through which students can submit and peer-review learning objects (articles, code, web sites, etc). The National Science Foundation supports the Expertiza project. It is used in select courses at NC State and by professors at several other colleges and universities.

Expertiza is a Ruby on Rails based open source project.

Problem Statement

Background

This project focuses on refactoring the StudentTask class to improve the organization, functionality, and separation of concerns. Currently, the class contains a mix of utility and class methods, many of which are static and should be moved into helper modules or relevant model classes, or broken down into separate, more maintainable methods. The goal is to make the class more efficient and follow best practices in object-oriented programming.

The StudentTask model is a significant part of the Expertiza website which maintains and handles student data who are participants under an assignment. Each assignment has a team, individual user participant and submission deadlines. The motive here is to refactor static methods called in the student_task.rb file and move them to helper classes for better object-oriented code design.

About StudentTask Model

The StudentTask model handles the tracking and organizes of student assignments, encapsulating key information about a student’s current progress, deadlines, and associated tasks. It provides different functional methods for task creation, checking completion status, and timeline generation, ensuring a streamlined and detailed view of a student's assignment timelines. The model interacts closely with assignment participants and topics to display their task completion stages, reviews, and deadlines.

Functionality

The StudentTask model manages student assignments with the help of functional methods to initialize tasks, track deadlines, and task assessment. It includes class method from_participant, which creates a task based on a participant’s data, and from_user, that generates a sorted list of tasks by deadline for a particular user. Task status is determined through methods such as complete?, incomplete?, and started?. Additionally The revision? method checks for ongoing updates based on current stage and submissions. The model also incorporates methods (such as get_due_date_data, get_submission_data, etc.) that compile task events in chronological order, enabling a clear overview of assignment deadlines, submissions, and peer reviews. Through encapsulation, delegation, and adherence to SRP, StudentTask ensures efficient and modular task tracking aligned with the required educational processes.

Functionality of StudentTaskHelper Module

The StudentTaskHelper module facilitates efficient task management by handling operations such as timeline generation, teammate grouping, and task initialization for participants. It includes generate_timeline, which combines due date, peer review, and author feedback timelines, as well as create_student_task_for_participant to initialize tasks based on participant data. The module introduces iterator patterns through methods such as for_each_due_date_of_assignment, for_each_peer_review, and for_each_author_feedback, which yield data to enhance flexibility and modularity within the codebase.

Helper methods like group_teammates_by_course_for_user efficiently organize teammates by course, while methods like parse_due_date_to_timeline and parse_response_to_timeline encapsulate logic for structuring timeline events. Using private helper methods such as fetch_response_from, and utility functions like calibration_assignment? and valid_assignment_team?, StudentTaskHelper optimizes data handling, improves readability, and adheres to object-oriented design principles by reducing the need for static methods in the StudentTask model.

Refactor

The StudentTask model do not strictly follow the SRP and DRY principles and needed a lot of refactoring to make them more manageable in following Object-Oriented design and development.

Files modified:

  • student_task.rb
  • student_task_controller.rb
  • student_task_helper.rb
  • student_task_spec.rb
  • assessment360_controller.rb
  • assessment360_controller_spec.rb

New files added:

  • student_task_helper_spec.rb

Login Details

Note

Switch from instructor view to student view, you can find it at the top of website.

Refactoring

self.from_participant(participant)

Creates a StudentTask instance for a given participant, populating it with relevant assignment and stage data.

Before:

After:

self.from_participantid(id)

It searches and fetches an active participant by its id.

Before:

After:

This method is depreciated during the refactoring process. It no longer served its purpose and was removed from the codebase.

self.from_user(user)

Retrieves tasks associated with a user by iterating over the user's assignment participants and sorting them by their stage deadlines.

Before:

After:

self.get_author_feedback_data(participant_id, timeline_list)

Generates a timeline of author feedback for a participant by mapping each feedback response through a parsing function.

Before:

After:

self.get_due_date_data(assignment, timeline_list)

Generates a timeline of due dates for an assignment by mapping each due date through a parsing function.

Before:

After:

self.peer_review_data(participant_id, timeline_list)

Iterates through each peer review for a participant, yielding each review to a provided block.

Before:

After:

self.get_submission_data(participant_id, team_id, timeline_list)

Before:

After:

This method, similar to from_participantid(), didn't showcase its usefulness during the refactoring process and was removed from the codebase.

self.get_timeline_data(assignment, participant, _team)

Generates a timeline of events related to the assignment for the participant, including due dates, peer reviews, and author feedback, sorted by update time.

Before:

After:

self.teamed_student(user, ip_address=nil)

Groups teammates by course for a user and returns a hash where keys are course IDs and values are lists of teammate names.

Before:

After:


Additional methods added:

Test Cases for StudentTask Model, Controller and helper classes

Descriptive test cases

  • Accessing the Assignments Page
   - Objective: Verify that the assignments page is accessible and displays all available assignments.
   - Steps: Navigate to the **Assignments** tab and confirm that it loads without errors, displaying the list of assignments.
  • Selecting an Assignment
   - Objective: Confirm that selecting an assignment redirects the user to the detailed view.
   - Steps: From the **Assignments** page, click on any assignment and verify that you are redirected to the respective **StudentTask** page for that assignment.
  • Viewing Assignment Details
   - Objective: Ensure that the **StudentTask** page displays assignment details, such as course, topic, and current stage.
   - Steps: On the **StudentTask** page, verify the presence of fields like course, topic, and current stage, ensuring accuracy in the information displayed.
  • Navigating "Your Work" and "Others' Work" Links
   - Objective: Test the links to "Your Work" and "Others' Work" sections to confirm they display relevant information.
   - Steps: Click the **Your Work** link to confirm it shows the student's submissions; then click the **Others' Work** link to verify it shows other students’ work or available feedback options, as applicable.
  • Checking Review Grade Information
   - Objective: Verify that the review grade, if available, is displayed along with any awarded badges.
   - Steps: Confirm the **Review Grade** section displays either a numeric grade or "N/A" if no grade is available, and ensure any badges appear with accurate images and tooltips.
  • Assignment Timelines and Deadline Tasks
   - Objective: Verify that each assignment timeline and associated deadlines are listed on the StudentTask page.
   - Steps: Navigate to the **Assignment Timelines** section and confirm that each task is listed in order with relevant due dates.
  • Checking and Sending Reviewer Emails
   - Objective: Test the "Send Email to Reviewers" feature to ensure it functions as expected.
   - Steps: On the **StudentTask** page, select the **Send Email to Reviewers** option and confirm the email interface opens, allowing the student to message reviewers.
  • Revisions and Unstarted Tasks
   - Objective: Verify that any revisions and unstarted tasks for the assignment are displayed accurately.
   - Steps: Check for sections labeled **Revisions** and **Tasks Not Yet Started**, and confirm that each section lists the student’s pending tasks correctly.
  • Viewing Teammates and Course Association
   - Objective: Confirm that teammates and course association are accurately displayed for assignments.
   - Steps: In the **Students Who Are Teamed with You** section, ensure teammates’ names and roles are displayed. Check that the assignment shows an association with a course name if applicable, or confirms if it is not associated with any course.

New RSpec Test Cases

1. To check each due date of assignment:

  describe '#for_each_due_date_of_assignment' do
   let(:due_date_modifier) do
     lambda { |dd|
       { label: (dd.deadline_type.name + ' Deadline').humanize,
         updated_at: dd.due_at.strftime('%a, %d %b %Y %H:%M') }
     }
   end
   context 'when called with assignment having empty due dates' do
     it 'return empty time_list array' do
       timeline_list = []
       student_task_helper.for_each_due_date_of_assignment(assignment) do |due_date|
         timeline_list << due_date_modifier.call(due_date)
       end
       expect(timeline_list).to eq([])
     end
   end
   context 'when called with assignment having due date' do
     context 'and due_at value nil' do
       it 'return empty time_list array' do
         allow(due_date).to receive(:deadline_type).and_return(deadline_type)
         timeline_list = []
         due_date.due_at = nil
         assignment.due_dates = [due_date]
         student_task_helper.for_each_due_date_of_assignment(assignment) { |due_date| timeline_list << due_date_modifier.call(due_date) }
         expect(timeline_list).to eq([])
       end
     end
     context 'and due_at value not nil' do
       it 'return time_list array' do
         allow(due_date).to receive(:deadline_type).and_return(deadline_type)
         timeline_list = []
         assignment.due_dates = [due_date]
         student_task_helper.for_each_due_date_of_assignment(assignment) do |due_date|
           timeline_list << due_date_modifier.call(due_date)
         end
         expect(timeline_list).to eq([{
                                       label: (due_date.deadline_type.name + ' Deadline').humanize,
                                       updated_at: due_date.due_at.strftime('%a, %d %b %Y %H:%M')
                                     }])
       end
     end
   end
 end

2. To check for peer reviews

 describe '#for_each_peer_review' do
   context 'when no review response mapped' do
     it 'returns empty' do
       timeline_list = []
       for_each_peer_review(user2) do |response|
         timeline_list << response_modifier.call(response, "Round #{response.round} Peer Review".humanize)
       end
       expect(timeline_list).to eq([])
     end
   end
   context 'when mapped to review response map' do
     it 'returns timeline array' do
       timeline_list = []
       allow(ReviewResponseMap).to receive_message_chain(:where, :find_each).with(reviewer_id: 1).with(no_args).and_yield(review_response_map)
       allow(review_response_map).to receive(:id).and_return(1)
       allow(Response).to receive_message_chain(:where, :last).with(map_id: 1).with(no_args).and_return(response)
       allow(response).to receive(:round).and_return(1)
       allow(response).to receive(:updated_at).and_return(Time.new(2019))
       timevalue = Time.new(2019).strftime('%a, %d %b %Y %H:%M')
       for_each_peer_review(1) do |resp|
         timeline_list << response_modifier.call(resp, "Round #{resp.round} Peer Review".humanize)
       end
       expect(timeline_list).to eq([{ id: 1, label: 'Round 1 peer review', updated_at: timevalue }])
     end
   end
 end

3. To check and validate author feedbacks

 describe '#for_each_author_feedback' do
   context 'when no feedback response mapped' do
     it 'returns empty' do
       timeline_list = []
       for_each_author_feedback(user2) do |response|
         timeline_list << response_modifier.call(response, 'Author feedback')
       end
       expect(timeline_list).to eq([])
     end
   end
   context 'when mapped to feedback response map' do
     it 'returns timeline array' do
       timeline_list = []
       allow(FeedbackResponseMap).to receive_message_chain(:where, :find_each).with(reviewer_id: 1).with(no_args).and_yield(review_response_map)
       allow(review_response_map).to receive(:id).and_return(1)
       allow(Response).to receive_message_chain(:where, :last).with(map_id: 1).with(no_args).and_return(response)
       allow(response).to receive(:updated_at).and_return(Time.now)
       timevalue = Time.now.strftime('%a, %d %b %Y %H:%M')
       timeline_list = []
       for_each_author_feedback(1) do |response|
         timeline_list << response_modifier.call(response, 'Author feedback')
       end
       expect(timeline_list).to eq([{ id: 1, label: 'Author feedback', updated_at: timevalue }])
     end
   end
 end

4. To create and validate generated timelines

describe '#generate_timeline' do
   context 'when no timeline data mapped' do
     it 'returns nil' do
       allow(participant).to receive(:get_reviewer).and_return(participant)
       expect(student_task_helper.generate_timeline(assignment, participant)).to eq([])
     end
   end
 end

5. Check if StudentTask obeject is created properly

 describe '#create_student_task_for_participant' do
   it 'creates a StudentTask with the correct attributes' do
     student_task = student_task_helper.create_student_task_for_participant(participant3)
     expect(student_task).to be_an_instance_of(StudentTask)
     expect(student_task.participant).to eq(participant3)
     expect(student_task.assignment).to eq(assignment)
     expect(student_task.topic).to eq(topic)
     expect(student_task.current_stage).to eq('submission')
     expect(student_task.stage_deadline).to eq(Time.parse('2024-12-31 12:00:00'))
   end
 end

6. To check the retrieved tasks for users

 describe '#retrieve_tasks_for_user' do
   before do
     allow(user).to receive_message_chain(:assignment_participants, :includes).and_return([participant4, participant5])
   end
   it 'retrieves and sorts tasks by stage_deadline' do
     tasks = student_task_helper.retrieve_tasks_for_user(user)
     expect(tasks.size).to eq(2)
     expect(tasks.first.stage_deadline).to eq(Time.parse('2024-11-01 12:00:00'))
     expect(tasks.last.stage_deadline).to eq(Time.parse('2024-12-01 12:00:00'))
   end
   it 'creates StudentTask objects for each participant' do
     tasks = student_task_helper.retrieve_tasks_for_user(user)
     tasks.each do |task|
       expect(task).to be_an_instance_of(StudentTask)
       expect(task.participant).to be_in([participant4, participant5])
       expect(task.assignment).to eq(assignment)
       expect(task.topic).to eq(topic)
     end
   end
 end

7. To check if deadlines are parsed properly

 describe '#parse_stage_deadline' do
   context 'If a valid time value is given' do
     it 'parse the provided time correctly' do
       given_time = '2024-12-31 12:00:00'
       parsed_time = student_task_helper.parse_stage_deadline(given_time)
       expect(parsed_time).to eq(Time.parse(given_time))
     end
   end
   context 'If given time string is invalid' do
     it 'return current time plus 1 year' do
       given_time = 'invalid-time-string'
       overhead_time = Time.now + 1.year
       parsed_time = student_task_helper.parse_stage_deadline(given_time)
       expect(parsed_time).to be_within(1.second).of(overhead_time)
     end
   end
 end

8. To check various conditions of users tagged as teammates

 describe '#group_teammates_by_course_for_user' do
   context 'when not in any team' do
     it 'returns empty' do
       expect(student_task_helper.group_teammates_by_course_for_user(user3)).to eq({})
     end
   end
   context 'when assigned in a course_team ' do
     it 'returns empty' do
       allow(user).to receive(:teams).and_return([course_team])
       expect(student_task_helper.group_teammates_by_course_for_user(user)).to eq({})
     end
   end
   context 'when assigned in a assignment_team ' do
     it 'returns the students they are teamed with' do
       allow(user).to receive(:teams).and_return([team])
       allow(AssignmentParticipant).to receive(:find_by).with(user_id: 1, parent_id: assignment.id).and_return(participant)
       allow(AssignmentParticipant).to receive(:find_by).with(user_id: 5, parent_id: assignment.id).and_return(participant2)
       allow(Assignment).to receive(:find_by).with(id: team.parent_id).and_return(assignment)
       expect(student_task_helper.group_teammates_by_course_for_user(user)).to eq(assignment.course_id => [user2.fullname])
     end
   end
 end

Team

Mentor

  • Ammana, Sahithi <sammana@ncsu.edu>

Members

  • Eathamukkala, Akarsh Reddy <aeatham@ncsu.edu>
  • Koul, Anmol <akoul2@ncsu.edu>
  • More, Harsh <hmore@ncsu.edu>

References

  1. Expertiza
  2. OSS Projects on Expertiza
  3. Github
  4. Pull Request