Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
6e2217c
Update README.md
riya-bihani Mar 17, 2025
8717094
Merge pull request #1 from expertiza/main
konetichandrakant Mar 19, 2025
bfc98c2
Created feedback_response_map.rb
Mar 24, 2025
e86b7d6
Added initial required methods in feedback_response_map.rb
Mar 24, 2025
beabf8f
Added round field to responses
Mar 24, 2025
818b097
Added dynamic round handling logic in feedback_response_map.rb
Mar 24, 2025
e9934ac
Merge pull request #5 from riya-bihani/feature/reimplement-feedback-r…
konetichandrakant Mar 24, 2025
1b17dd9
Added skeleton and initial base testcases to feedback_response_map_sp…
Mar 24, 2025
0b932f2
Merge pull request #6 from riya-bihani/test/reimplementation-feedback…
konetichandrakant Mar 24, 2025
2a7b736
Corrected few codes and added questionnaire testcase in feedback_resp…
Mar 24, 2025
b7d2d46
Added testcases for other methods in feedback_response_map_spec.rb
Mar 24, 2025
6b15430
Merge pull request #7 from riya-bihani/test/reimplementation-feedback…
konetichandrakant Mar 24, 2025
0019a7f
Controler and CRUD Operations created
NDT2000 Mar 25, 2025
55d7233
Update database.yml
NDT2000 Mar 25, 2025
059e18a
Modified testcases logic on feedback_response_map_spec.rb
Mar 25, 2025
d62a635
Merge pull request #8 from riya-bihani/test/reimplementation-feedback…
konetichandrakant Mar 25, 2025
55f3663
new way to separate email functionality
riya-bihani Apr 17, 2025
ba2dfd3
calling the email in the feedback map
riya-bihani Apr 17, 2025
68faab2
testing the separated email functionality
riya-bihani Apr 18, 2025
6943d2d
working on separating the email functionality from feedback_response_…
riya-bihani Apr 18, 2025
520b9a0
testing the separated email functionality
riya-bihani Apr 18, 2025
1297ec8
Added intial methods in response map controller
Apr 18, 2025
e4e52b8
Merge pull request #11 from riya-bihani/feature/refactor-response-map
konetichandrakant Apr 18, 2025
d2e6a26
Integrated email functionality into ResponseMapController using the e…
Apr 18, 2025
079b248
Merge pull request #12 from riya-bihani/feature/refactor-response-map
konetichandrakant Apr 18, 2025
4bef586
Modified response model structure
Apr 20, 2025
4ca45f1
Merge pull request #13 from riya-bihani/feature/refactor-response
konetichandrakant Apr 20, 2025
f9cd7e7
Refactored response_map model to follow a centralized pattern for all…
Apr 20, 2025
50b2559
Merge pull request #14 from riya-bihani/feature/refactor-response-map
konetichandrakant Apr 20, 2025
5cc2850
feat: call FeedbackEmailService in submit_response
Apr 20, 2025
877f4fe
Merge pull request #15 from riya-bihani/feature/refactor-response-map
konetichandrakant Apr 20, 2025
5ae94ac
chore: allow submitted flag in strong params
Apr 20, 2025
ef1486b
refactor: DRY out email logic into private method
Apr 20, 2025
beb697c
feat: send email on create/update when submitted flag set
Apr 20, 2025
66cfec7
Merge branch 'feature/refactor-response-map'
Apr 20, 2025
4c20c2e
first unit test case added
NDT2000 Apr 21, 2025
1c102e5
Important changes made
NDT2000 Apr 21, 2025
1ae9b19
Test cases added
NDT2000 Apr 21, 2025
0f99cd7
test case added
NDT2000 Apr 21, 2025
7deb218
Refactor 2 test cases
NDT2000 Apr 21, 2025
c2cb692
Third test case added
NDT2000 Apr 21, 2025
6e71228
adding meaningful comments to the code
riya-bihani Apr 22, 2025
17a1cf5
Added logic to feedback_response_map_controller
Apr 22, 2025
21269b0
Merge pull request #16 from riya-bihani/refactor/feedback-response-ma…
konetichandrakant Apr 22, 2025
b3d750d
Merge remote-tracking branch 'origin/main' into test/response-map-con…
Apr 22, 2025
4a077c9
Merge pull request #17 from riya-bihani/test/response-map-controller
NDT2000 Apr 23, 2025
f4ea18c
Add inline comments throughout the code for improved clarity and main…
Apr 23, 2025
5cbed09
implementing the changes suggested - moving the email file
riya-bihani Apr 26, 2025
156ab96
Refactored the codebase by relocating FeedbackEmailService to the mai…
Apr 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ alt="IMAGE ALT TEXT HERE" width="560" height="315" border="10" /></a>
### Database Credentials
- username: root
- password: expertiza


Project 2509 - Riya Bihani, Chandrakant Koneti, Nayan Taori
78 changes: 78 additions & 0 deletions app/controllers/api/v1/feedback_response_maps_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
module Api
module V1
# Handles operations specific to feedback response maps
# Inherits from ResponseMapsController to leverage common functionality
# while providing specialized behavior for feedback
class FeedbackResponseMapsController < ResponseMapsController
# Overrides the base controller's set_response_map method
# to specifically look for FeedbackResponseMap instances
# @raise [ActiveRecord::RecordNotFound] if the feedback response map isn't found
def set_response_map
@response_map = FeedbackResponseMap.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: { error: 'Feedback response map not found' }, status: :not_found
end

# Retrieves all feedback response maps for a specific assignment
# Useful for instructors to monitor feedback activity
# GET /api/v1/feedback_response_maps/assignment/:assignment_id
def assignment_feedback
@feedback_maps = FeedbackResponseMap
.joins(:assignment)
.where(assignments: { id: params[:assignment_id] })
render json: @feedback_maps
end

# Gets all feedback maps for a specific reviewer
# Includes the associated responses for comprehensive feedback history
# GET /api/v1/feedback_response_maps/reviewer/:reviewer_id
def reviewer_feedback
@feedback_maps = FeedbackResponseMap
.where(reviewer_id: params[:reviewer_id])
.includes(:responses)
render json: @feedback_maps, include: :responses
end

# Calculates and returns feedback response statistics for an assignment
# Includes total maps, completed maps, and response rate percentage
# GET /api/v1/feedback_response_maps/response_rate/:assignment_id
def feedback_response_rate
assignment_id = params[:assignment_id]
total_maps = FeedbackResponseMap
.joins(:assignment)
.where(assignments: { id: assignment_id })
.count

completed_maps = FeedbackResponseMap
.joins(:assignment)
.where(assignments: { id: assignment_id })
.joins(:responses)
.where(responses: { is_submitted: true })
.distinct
.count

render json: {
total_feedback_maps: total_maps,
completed_feedback_maps: completed_maps,
response_rate: total_maps > 0 ? (completed_maps.to_f / total_maps * 100).round(2) : 0
}
end

private

# Defines permitted parameters specific to feedback response maps
# @return [ActionController::Parameters] Whitelisted parameters
def response_map_params
params.require(:feedback_response_map).permit(:reviewee_id, :reviewer_id, :reviewed_object_id)
end

# Ensures that we create a FeedbackResponseMap instance
# instead of a base ResponseMap
# POST /api/v1/feedback_response_maps
def create
@response_map = FeedbackResponseMap.new(response_map_params)
persist_and_respond(@response_map, :created)
end
end
end
end
118 changes: 118 additions & 0 deletions app/controllers/api/v1/response_maps_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# app/controllers/api/v1/response_maps_controller.rb
# Handles CRUD operations and special actions for ResponseMaps
# ResponseMaps represent the relationship between a reviewer and reviewee
class Api::V1::ResponseMapsController < ApplicationController
before_action :set_response_map, only: [:show, :update, :destroy, :submit_response]

# Lists all response maps in the system
# GET /api/v1/response_maps
def index
@response_maps = ResponseMap.all
render json: @response_maps
end

# Retrieves a specific response map by ID
# GET /api/v1/response_maps/:id
def show
render json: @response_map
end

# Creates a new response map with the provided parameters
# POST /api/v1/response_maps
def create
@response_map = ResponseMap.new(response_map_params)
persist_and_respond(@response_map, :created)
end

# Updates an existing response map with new attributes
# PATCH/PUT /api/v1/response_maps/:id
def update
@response_map.assign_attributes(response_map_params)
persist_and_respond(@response_map, :ok)
end

# Removes a response map from the system
# DELETE /api/v1/response_maps/:id
def destroy
@response_map.destroy
head :no_content
end

# Handles the submission of a response associated with a response map
# This also triggers email notifications if configured
# POST /api/v1/response_maps/:id/submit_response
def submit_response
@response = @response_map.responses.find_or_initialize_by(id: params[:response_id])
@response.assign_attributes(response_params)
@response.is_submitted = true

if @response.save
# send feedback email now that it’s marked submitted
FeedbackEmailMailer.new(@response_map, @response_map.assignment).call
render json: { message: 'Response submitted successfully, email sent' }, status: :ok
handle_submission(@response_map)
else
render json: { errors: @response.errors }, status: :unprocessable_entity
end
end

# Processes the actual submission and handles email notifications
# @param map [ResponseMap] The response map being submitted
def handle_submission(map)
FeedbackEmailMailer.new(map, map.assignment).call
render json: { message: 'Response submitted successfully, email sent' }, status: :ok
rescue StandardError => e
Rails.logger.error "FeedbackEmail failed: #{e.message}"
render json: { message: 'Response submitted, but email failed' }, status: :ok
end

# Generates a report of responses for a specific assignment
# Can be filtered by type and grouped by rounds if applicable
# GET /api/v1/response_maps/response_report/:assignment_id
def response_report
assignment_id = params[:assignment_id]
report = ResponseMap.response_report(assignment_id, params[:type])
render json: report
end

private

# Locates the response map by ID and sets it as an instance variable
# Renders a 404 error if the map is not found
def set_response_map
@response_map = ResponseMap.find(params[:id])
rescue ActiveRecord::RecordNotFound
render json: { error: 'Response map not found' }, status: :not_found
end

# Defines permitted parameters for response map creation/update
# @return [ActionController::Parameters] Whitelisted parameters
def response_map_params
params.require(:response_map).permit(:reviewee_id, :reviewer_id, :reviewed_object_id)
end

# Defines permitted parameters for response submission
# Includes nested attributes for scores
# @return [ActionController::Parameters] Whitelisted parameters
def response_params
params.require(:response).permit(
:additional_comment,
:round,
:is_submitted,
scores_attributes: [:answer, :comments, :question_id]
)
end

# Common method to persist records and generate appropriate responses
# Handles submission processing if the record is marked as submitted
# @param record [ActiveRecord::Base] The record to save
# @param success_status [Symbol] HTTP status code for successful save
def persist_and_respond(record, success_status)
if record.save
handle_submission(record) if record.is_submitted?
render json: record, status: success_status
else
render json: record.errors, status: :unprocessable_entity
end
end
end
33 changes: 33 additions & 0 deletions app/mailers/feedback_email_mailer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class FeedbackEmailMailer < ApplicationMailer
# Initialize the mailer with a ResponseMap and an Assignment
def initialize(response_map, assignment)
@response_map = response_map
@assignment = assignment
end

# Public API method to trigger the email send
def call
Mailer.sync_message(build_defn).deliver
end

private

def build_defn
# find the original review response
response = Response.find(@response_map.reviewed_object_id)
# find the ResponseMap that created that review
original_map = ResponseMap.find(response.map_id)
# find the participant who wrote that review
participant = AssignmentParticipant.find(original_map.reviewer_id)
user = User.find(participant.user_id)

{
to: user.email,
body: {
type: 'Author Feedback',
first_name: user.fullname,
obj_name: @assignment.name
}
}
end
end
81 changes: 81 additions & 0 deletions app/models/feedback_response_map.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
class FeedbackResponseMap < ResponseMap
# old implementation improvements and corrections
belongs_to :reviewee, class_name: 'Participant', foreign_key: 'reviewee_id'
belongs_to :reviewer, class_name: 'AssignmentParticipant', dependent: :destroy
belongs_to :review, class_name: 'Response', foreign_key: 'reviewed_object_id'

# Returns the title used for display
def title
'Feedback'
end

# Gets the feedback questionnaire associated with the assignment
def questionnaire
self.assignment.questionnaires
end

# Returns the original contributor (the author who received the review)
def contributor
self.reviewee
end

# # Returns the team being reviewed in the original review
# def team
# self.reviewee_team
# end

# Returns the reviewer who gave the original review
def reviewer
self.reviewer
end

# Returns the round number of the original review (if applicable)
def round
self&.response&.round
end

# Returns a report of feedback responses, grouped dynamically by round
def self.feedback_response_report(assignment_id, _type)
authors = fetch_authors_for_assignment(assignment_id)
review_map_ids = review_map_ids = ReviewResponseMap.where(["reviewed_object_id = ?", assignment_id]).pluck("id")
review_responses = Response.where(["map_id IN (?)", review_map_ids])
review_responses = review_responses.order("created_at DESC") if review_responses.respond_to?(:order)

if Assignment.find(assignment_id).varying_rubrics_by_round?
latest_by_map_and_round = {}

review_responses.each do |response|
key = [response.map_id, response.round]
latest_by_map_and_round[key] ||= response
end

grouped_by_round = latest_by_map_and_round.values.group_by(&:round)
sorted_by_round = grouped_by_round.sort.to_h # {round_number => [response1_id, response2_id, ...]}
response_ids_by_round = sorted_by_round.transform_values { |resps| resps.map(&:id) }

[authors] + response_ids_by_round.values
else
latest_by_map = {}

review_responses.each do |response|
latest_by_map[response.map_id] ||= response
end

[authors, latest_by_map.values.map(&:id)]
end
end

# Fetches all participants who authored submissions for the assignment
def self.fetch_authors_for_assignment(assignment_id)
Assignment.find(assignment_id).teams.includes(:users).flat_map do |team|
team.users.map do |user|
AssignmentParticipant.find_by(parent_id: assignment_id, user_id: user.id)
end
end.compact
end

def send_feedback_email(assignment)
FeedbackEmailMailer.new(self, assignment).call
end

end
Loading