Skip to content

Review report#351

Open
aanand-1706 wants to merge 42 commits into
expertiza:mainfrom
aanand-1706:review-report
Open

Review report#351
aanand-1706 wants to merge 42 commits into
expertiza:mainfrom
aanand-1706:review-report

Conversation

@aanand-1706

@aanand-1706 aanand-1706 commented May 25, 2026

Copy link
Copy Markdown
Contributor

Code Review Assessment Against Expertiza Danger Policy

🔴 Critical Violations

Missing Test Coverage (new production code, no tests)

  • Substantial new code (ReportsController + Reports::* classes and new ActiveRecord scopes) added with no corresponding specs. Repository search found no specs referencing ReportsController, Reports::BaseReport, Reports::ReviewReport, or the new scopes.
    • Required specs to add:
      • spec/controllers/reports_controller_spec.rb — auth, valid/invalid type, 404/422/500 branches.
      • spec/models/reports/base_report_spec.rb — streaming/reducer contract (source/initial_state/accumulate/finalize), run semantics.
      • spec/models/reports/review_report_spec.rb — ReviewersReducer, ScoresReducer, AvgRangesReducer behavior and integration.
      • Specs for Response.submitted_review_responses_for and ReviewResponseMap.for_assignment.

Authorization & Error-Path Tests Missing

  • ReportsController.action_allowed? restricts to assignment TAs but there are no authorization tests to verify access control (TA vs non-TA).
  • No tests for error branches: missing assignment (404), invalid report type (422), and generic rescue (500).

🟡 Medium Severity Issues

Overly Broad Error Handling

  • ReportsController rescues StandardError and returns 500 JSON. This masks unexpected failures and reduces observability. Prefer rescuing specific exceptions or letting unexpected errors surface (with logging/monitoring) and test error behavior.

Unrelated/Whitespace Churn

  • Raw changes included a whitespace-only edit in app/models/Item.rb (move to separate PR to avoid noise). Routes file shows formatting changes plus a new POST /reports/fetch_report (config/routes.rb:199) — minimize non-functional formatting churn.

Potential Performance Concerns

  • New streaming reducer pattern is reasonable, but no performance or query-count tests. Ensure eager-loading covers all associations used by reducers and add tests/assertions for N+1 or memory growth on larger fixtures.

Danger/Debrief Signals Present

  • Repo contains TODO comments (responses_controller, authorization) and existing debug patterns that the Danger rules check for. This PR modifies app/ without adding specs, which will trigger the repository's Dangerfile checks.

🟢 Low Severity / Checklist

  • No schema or migration files were added; confirm referenced DB columns/indexes (reviewed_object_id, response_map_id, round, any aggregate fields) already exist — db/schema.rb unchanged.
  • New routes added: POST /reports/fetch_report present in config/routes.rb.
  • No debug prints introduced in the new files per current search.

Summary / Recommendation

Status: CANNOT MERGE

Required before merge:

  1. Add comprehensive specs (controller + model reducers + scope tests) listed above.
  2. Add authorization tests for TA access and negative cases.
  3. Replace the broad StandardError rescue with narrower handling or re-raise after logging; add tests for error branches.
  4. Remove or separate unrelated whitespace change (Item.rb) into its own PR and revert unnecessary formatting churn in routes.
  5. Add basic performance/query-count tests or benchmarks for reducer flows to guard against N+1/memory issues.

The implementation and reducer design are plausible, but Expertiza Danger policy and repo standards require test coverage, tighter error handling, and avoidance of unrelated churn before approval.

Review Change Stack

asreeku and others added 24 commits April 27, 2026 12:47
Ports feedback_response_map report from Expertiza (repo X) to the
JSON API architecture of repo Y using a streaming reduce pipeline.

- FeedbackResponseMap: adds feedback_response_report class method
  (deduplicates responses per map/round, buckets by round for
  varying-rubric assignments)
- BaseReport: new abstract pipeline base (source -> grouper ->
  accumulate -> finalize via find_each streaming)
- FeedbackReport: concrete pipeline subclass; uses Set for O(1)
  deduplication; fetches authors once in finalize
- ReportsController: replaces send(type) dispatch with
  REPORT_CLASSES hash; renders JSON from report.run
- ReportFormatterHelper: emptied (logic moved to services)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FeedbackReport pipeline handles all querying, deduplication, and
round bucketing directly — the model class method is no longer called.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DeploymentPipeline now inherits BaseReport, streaming TeamsUser grouped
  by user_id instead of nested team → user loops
- Precompute taggable answer IDs and tags before the hot loop (zero DB
  queries in accumulate)
- Remove varying_rubrics_by_round? branching — item_ids filter handles
  rubric scoping at the answer level
- Extract model scopes: AnswerTag.for_deployment, TeamsUser.for_assignment,
  Answer.for_items_and_responses
- Add pipeline benefit comment to BaseReport#run

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Item.for_questionnaire_and_type scope
- Add Response.submitted_review_responses_for scope; reuse in ReviewReport
- Fix block indentation in AnswerTaggingReport#run
- Fix Response chain indentation in fetch_responses_received_by_teams

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract ReviewResponsePipelineShared module with shared source and
  precompute_max_q_scores, included by ScoresPipeline and AvgRangesPipeline
- Filter max question scores to ReviewQuestionnaire type only
- Refactor AvgRangesPipeline to group by reviewee_id with round extracted
  in accumulate; simplify finalize with transform_values
- Improve initial_state readability with named block params and comments
- Remove unused for_course factory method
- Rename terse lambda params (r → reviewer, response_map, response)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ReviewResponseMap.for_assignment scope
- Add AssignmentQuestionnaire.for_assignment_and_type scope (generic, reusable)
- Use scopes in ReviewersPipeline source and precompute_max_q_scores

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace single DeploymentPipeline with TaggableAnswersPipeline and
TaggingStatsPipeline. TaggableAnswersPipeline streams taggable Answer
rows accumulating answer_ids and response_ids per user via a new
Answer.taggable_for_assignment scope. TaggingStatsPipeline streams
AnswerTag rows with response context; finalize filters by response_id
membership instead of preloading all tags into memory. UserSummaryPipeline
now keys by user_id to avoid collisions for users with identical names.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…sponse_id

TaggableAnswersPipeline state simplified from {answer_ids, response_ids}
to a plain array of answer_ids. Finalize converts to a Set and filters
AnswerTags directly by answer_id membership, removing the response_id
indirection and the unused response_ids Set from the pipeline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix NameError bug (return unless r → return unless reviewer) in
ReviewersPipeline. Remove stale grouper comment. Move max_q_scores
precomputation from each pipeline's initialize into ReviewReport#run
so it runs once and is passed to both ScoresPipeline and AvgRangesPipeline,
eliminating the duplicate DB query. ReviewResponsePipelineShared now
only holds the shared source definition.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add explanation of grouper's separation of concerns, how BaseReport#run
wires grouper key into accumulate, and concrete examples from
ScoresPipeline, AvgRangesPipeline, and TaggableAnswersPipeline.
Also document that all domain math belongs in accumulate, not the base.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…atest submission

Rename grouper → state_key_for across all report aggregators and BaseReport
to better express its role: given a row, return the key used to look it up
in the accumulated state.

Update submitted_review_responses_for scope to return only the latest
submitted response per (response_map, round) via a MAX(id) subquery,
ensuring that only the most recent submission per reviewer-reviewee pair
per round is counted in ScoresAggregator and AvgRangesAggregator.

Rename Pipeline → Aggregator in all comments and class references across
answer_tagging_report.rb, review_report.rb, and base_report.rb.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Store all reviewees for each reviewer instead of keeping only one entry
per reviewer. Eagerly load reviewee: :user alongside reviewer: :user to
avoid N+1 queries. Rename participant → reviewer for clarity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…accumulate

Add caveat to accumulate description noting that the key is passed but not
enforced — subclasses are trusted to use it as the state bucket key.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BaseReport#run now accepts an optional shared_state parameter so multiple
aggregators can write into the same hash without a merge loop. When shared_state
is provided, initial_state is ignored and finalize is skipped.

TeammateReviewReport refactored into two aggregators sharing state keyed by
user_id: TeammateReviewAggregator fills reviewer info, reviewees, and
reviewed_count; TeamSizeAggregator fills teammate_count. Full state shape
initialized upfront in Hash.new block for consistency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Merge ReviewersReducer and ScoresReducer into a shared state keyed by
reviewer_id so reviewer info and scores are co-located without a merge
loop. ScoresReducer now delegates to calculate_total_score/maximum_score
(ScorableHelper) with scores and questionnaires eagerly loaded to avoid
N+1. AvgRangesReducer simplified to use aggregate_review_grade with full
eager loading. Rename Aggregator → Reducer across all report files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove redundant "reducer" from BaseReport class description
- Fix AvgRangesReducer example: groups by team_id, not reviewee_id
- Clarify finalize-always-called sentence in BaseReport#run
- Fix teammate_review_report state key comments: reviewer_id → user_id
- Rename leftover Aggregator → Reducer in teammate_review_report comments and class names

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ntroller

- Register AnswerTaggingReport under the 'answer_tagging' type key
- Switch from report_class.new(assignment) to report_class.for_assignment(assignment)
  to match the factory method convention used by all report classes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove GET route; keep only POST /reports/fetch_response_report
- Rename action from response_report to fetch_response_report
- Drop params[:id] fallback and params.dig(:report, :type) nesting —
  JSON API clients send flat { assignment_id:, type: } body

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a Reports API endpoint guarded by TA privileges, a streaming Reports::BaseReport scaffold, a concrete Reports::ReviewReport composed of three reducers (reviewers, scores, avg ranges), supporting ActiveRecord scopes, and registers POST /reports/fetch_report.

Changes

Review Report Generation Feature

Layer / File(s) Summary
Base Report Infrastructure
app/models/reports/base_report.rb
Reports::BaseReport provides an abstract reducer scaffold with streaming iteration via find_each, mutable accumulator initialization/finalization, and factory methods for assignment/course instances.
Review Report & Reducers
app/models/reports/review_report.rb
Reports::ReviewReport orchestrates three reducers: ReviewersReducer streams review mappings to populate reviewer identities, ScoresReducer aggregates score percentages per reviewer/reviewee/round, and AvgRangesReducer computes team-level aggregates; run returns sorted reviewers and avg_and_ranges.
Supporting Model Scopes and Minor Item Edit
app/models/response.rb, app/models/review_response_map.rb, app/models/Item.rb
Adds Response.submitted_review_responses_for(assignment_id) to select latest submitted responses per response_map and round; adds ReviewResponseMap.for_assignment(assignment_id); minor formatting tweak in aggregate_questionnaire_score; whitespace-only change in Item.
Reports Controller & Routing
app/controllers/reports_controller.rb, config/routes.rb
Adds ReportsController with REPORT_CLASSES, TA-only action_allowed?, and fetch_report endpoint that validates type, loads assignment, runs the report, and returns JSON responses/errors; registers POST /reports/fetch_report and performs minor route whitespace edits.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

api, database

🚥 Pre-merge checks | ✅ 12 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Behavior-Change-Needs-Tests ⚠️ Warning 260 lines of behavioral code added (ReportsController, BaseReport, ReviewReport, 5+ model changes) without test coverage in spec/models/ or spec/requests/. Add spec/requests/reports_controller_spec.rb and spec/models/reports/ for new code; update response_spec.rb and add review_response_map_spec.rb for scope tests.
✅ Passed checks (12 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Review report' clearly identifies the main subsystem changed (review reporting functionality) and aligns with the primary changes across the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Schema-Without-Migration ✅ Passed No database schema changes detected in this PR; db/schema.rb was not modified, so the check condition does not apply.
Workflow-Security ✅ Passed No GitHub Actions workflow files are modified in this PR; all changes are to application code only (controllers, models, routes).
Config-And-Setup-Scrutiny ✅ Passed No critical config/setup files (database.yml, storage.yml, credentials.yml.enc, config.ru, setup.sh, or workflows) were modified in this PR.
Todo-Temp-Debug-Artifacts ✅ Passed No TODO/FIXME markers, debug print statements, or new temp artifacts (results.txt, safe.log) are introduced in the PR files.
Legacy-Pr-Scope-And-Title ✅ Passed PR has 307 LoC across 7 files, no WIP title, no duplicate commits; all criteria below thresholds for legacy PR warnings.
Legacy-Config-File-Guardrails ✅ Passed No legacy config files (Gemfile, Gemfile.lock, .gitignore, .rspec, Dangerfile, Rakefile, config.ru, setup.sh, YAML files, Markdown files, vendor/, or spec/factories/) were modified in this PR.
Legacy-Rspec-Hygiene ✅ Passed PR adds no new or modified spec files, so legacy-rspec-hygiene check (which applies only to specs) is not applicable.
Legacy-Global-Debug-Code ✅ Passed No newly introduced global variables, class variables, or debugging statements found in new files (reports_controller.rb, base_report.rb, review_report.rb) and modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

github-actions Bot commented May 25, 2026

Copy link
Copy Markdown
1 Warning
⚠️ There are code changes, but no corresponding tests.
Please include tests if this PR introduces any modifications in behavior.
You are including debug code in your pull request, please remove it.
You should commit changes to the DB schema (db/schema.rb) only if you have created new DB migrations.
Please double check your code. If you did not aim to change the DB, please revert the DB schema changes.
1 Message
📖 Thanks for the pull request, and welcome! The Expertiza team is excited to review your changes, and you should hear from us soon.

Please make sure the PR passes all checks and you have run rubocop -a to autocorrect issues before requesting a review.

This repository is being automatically checked for code-quality issues using CodeRabbit, Danger, and CI workflows. Please address newly introduced issues before marking the PR ready for review.

If you have any questions, please send email to expertiza-support@lists.ncsu.edu.

Thanks for the pull request, and welcome! The Expertiza team is excited to review your changes, and you should hear from us soon.

Please make sure the PR passes all checks and you have run rubocop -a to autocorrect issues before requesting a review.

This repository is being automatically checked for code-quality issues using CodeRabbit, Danger, and CI workflows. Please address newly introduced issues before marking the PR ready for review.

If you have any questions, please send email to expertiza-support@lists.ncsu.edu.

There are code changes, but no corresponding tests.
Please include tests if this PR introduces any modifications in behavior.

Generated by 🚫 Danger

@github-actions

Copy link
Copy Markdown

🚨 RSpec Tests Report

All tests passed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9851c939-c032-4dc4-b6d4-f4a433e94533

📥 Commits

Reviewing files that changed from the base of the PR and between 3eabaf9 and c4274fc.

📒 Files selected for processing (7)
  • app/controllers/reports_controller.rb
  • app/models/Item.rb
  • app/models/reports/base_report.rb
  • app/models/reports/review_report.rb
  • app/models/response.rb
  • app/models/review_response_map.rb
  • config/routes.rb
📜 Review details
⏰ Context from checks skipped due to timeout of 300000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: test
🧰 Additional context used
📓 Path-based instructions (3)
app/models/**/*.rb

⚙️ CodeRabbit configuration file

app/models/**/*.rb: Focus on associations, validations, callbacks, transactions, STI and polymorphic behavior,
foreign keys, data integrity, and query efficiency.

Files:

  • app/models/response.rb
  • app/models/review_response_map.rb
  • app/models/reports/base_report.rb
  • app/models/Item.rb
  • app/models/reports/review_report.rb
app/controllers/**/*.rb

⚙️ CodeRabbit configuration file

app/controllers/**/*.rb: Review authorization, strong parameters, HTTP status codes, and response shape consistency.
Flag N+1 queries, missing request-spec coverage, and behavior changes not reflected in Swagger docs.

Files:

  • app/controllers/reports_controller.rb
config/routes.rb

⚙️ CodeRabbit configuration file

config/routes.rb: Flag duplicate or shadowed routes, surprising non-RESTful patterns, and route changes without matching request specs.

Files:

  • config/routes.rb
🧬 Code graph analysis (3)
app/controllers/reports_controller.rb (1)
app/controllers/concerns/authorization.rb (1)
  • current_user_has_ta_privileges? (57-59)
app/models/reports/base_report.rb (1)
app/models/reports/review_report.rb (1)
  • for_assignment (17-132)
app/models/reports/review_report.rb (3)
app/models/reports/base_report.rb (3)
  • run (77-83)
  • accumulate (91-93)
  • initialize (59-61)
app/models/response.rb (1)
  • include (3-121)
app/models/review_response_map.rb (1)
  • include (2-30)
🪛 RuboCop (1.86.2)
app/models/response.rb

[convention] 9-9: Use the lambda method for multiline lambdas.

(Style/Lambda)


[convention] 13-13: Align .where with joins(:response_map) on line 12.

(Layout/MultilineMethodCallIndentation)


[convention] 17-17: Align .group with joins(:response_map) on line 12.

(Layout/MultilineMethodCallIndentation)


[convention] 18-18: Align .select with joins(:response_map) on line 12.

(Layout/MultilineMethodCallIndentation)


[convention] 105-105: Missing space after #.

(Layout/LeadingCommentSpace)

app/controllers/reports_controller.rb

[convention] 5-5: Align the keys of a hash literal if they span more than one line.

(Layout/HashAlignment)


[convention] 6-6: Align the keys of a hash literal if they span more than one line.

(Layout/HashAlignment)


[convention] 7-7: Align the keys of a hash literal if they span more than one line.

(Layout/HashAlignment)


[convention] 8-8: Align the keys of a hash literal if they span more than one line.

(Layout/HashAlignment)


[convention] 9-9: Align the keys of a hash literal if they span more than one line.

(Layout/HashAlignment)


[convention] 10-10: Align the keys of a hash literal if they span more than one line.

(Layout/HashAlignment)

app/models/reports/base_report.rb

[convention] 87-87: Unnecessary spacing detected.

(Layout/ExtraSpacing)


[convention] 88-88: Unnecessary spacing detected.

(Layout/ExtraSpacing)

config/routes.rb

[convention] 80-97: Inconsistent indentation detected.

(Layout/IndentationConsistency)


[convention] 81-81: Use 2 (not 4) spaces for indentation.

(Layout/IndentationWidth)


[convention] 97-97: end at 97, 6 is not aligned with resources :review_mappings, only: [] do at 80, 5.

(Layout/BlockAlignment)


[convention] 99-109: Inconsistent indentation detected.

(Layout/IndentationConsistency)


[convention] 104-108: Inconsistent indentation detected.

(Layout/IndentationConsistency)


[convention] 108-108: end at 108, 8 is not aligned with member do at 104, 9.

(Layout/BlockAlignment)


[convention] 110-110: Trailing whitespace detected.

(Layout/TrailingWhitespace)


[convention] 169-169: Trailing whitespace detected.

(Layout/TrailingWhitespace)


[convention] 172-172: Trailing whitespace detected.

(Layout/TrailingWhitespace)


[convention] 196-196: Trailing whitespace detected.

(Layout/TrailingWhitespace)


[convention] 197-201: Inconsistent indentation detected.

(Layout/IndentationConsistency)


[convention] 204-204: Trailing whitespace detected.

(Layout/TrailingWhitespace)


[convention] 3-228: Block has too many lines. [201/120]

(Metrics/BlockLength)


[convention] 228-228: Final newline missing.

(Layout/TrailingEmptyLines)

app/models/reports/review_report.rb

[convention] 29-29: Align the keys of a hash literal if they span more than one line.

(Layout/HashAlignment)


[convention] 30-30: Align the keys of a hash literal if they span more than one line.

(Layout/HashAlignment)


[convention] 31-31: Align the keys of a hash literal if they span more than one line.

(Layout/HashAlignment)


[convention] 33-33: Align the keys of a hash literal if they span more than one line.

(Layout/HashAlignment)


[convention] 34-34: Align the keys of a hash literal if they span more than one line.

(Layout/HashAlignment)


[convention] 42-42: Align the keys of a hash literal if they span more than one line.

(Layout/HashAlignment)


[convention] 68-68: Pass &:reviewer_id as an argument to lambda instead of a block.

(Style/SymbolProc)


[convention] 72-83: Assignment Branch Condition size for accumulate is too high. [<6, 19, 4> 20.32/20]

(Metrics/AbcSize)


[convention] 124-124: Pass &:id as an argument to lambda instead of a block.

(Style/SymbolProc)

🔇 Additional comments (1)
app/models/Item.rb (1)

8-8: LGTM!

Comment thread app/controllers/reports_controller.rb Outdated
Comment on lines +21 to +35
assignment_id = params[:assignment_id]
type = params[:type] || 'basic'

report_class = REPORT_CLASSES[type]
unless report_class
return render json: {
error: "Unknown report type: #{type}. Valid types: #{REPORT_CLASSES.keys.join(', ')}"
}, status: :unprocessable_entity
end

assignment = Assignment.find(assignment_id)
data = report_class.for_assignment(assignment).run
render json: { type: type, assignment_id: assignment_id.to_i }.merge(data)
rescue ActiveRecord::RecordNotFound
render json: { error: 'Assignment not found' }, status: :not_found

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return 400/422 for invalid assignment_id instead of 404.

If assignment_id is missing/blank, Line 31 raises RecordNotFound and returns Line 35’s 404, which misclassifies a malformed request as “not found”.

Proposed fix
   def fetch_response_report
-    assignment_id = params[:assignment_id]
+    assignment_id = params[:assignment_id]
+    if assignment_id.blank?
+      return render json: { error: 'assignment_id is required' }, status: :bad_request
+    end
+
     type = params[:type] || 'basic'

As per coding guidelines "app/controllers/**/*.rb: Review ... HTTP status codes."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assignment_id = params[:assignment_id]
type = params[:type] || 'basic'
report_class = REPORT_CLASSES[type]
unless report_class
return render json: {
error: "Unknown report type: #{type}. Valid types: #{REPORT_CLASSES.keys.join(', ')}"
}, status: :unprocessable_entity
end
assignment = Assignment.find(assignment_id)
data = report_class.for_assignment(assignment).run
render json: { type: type, assignment_id: assignment_id.to_i }.merge(data)
rescue ActiveRecord::RecordNotFound
render json: { error: 'Assignment not found' }, status: :not_found
assignment_id = params[:assignment_id]
if assignment_id.blank?
return render json: { error: 'assignment_id is required' }, status: :bad_request
end
type = params[:type] || 'basic'
report_class = REPORT_CLASSES[type]
unless report_class
return render json: {
error: "Unknown report type: #{type}. Valid types: #{REPORT_CLASSES.keys.join(', ')}"
}, status: :unprocessable_entity
end
assignment = Assignment.find(assignment_id)
data = report_class.for_assignment(assignment).run
render json: { type: type, assignment_id: assignment_id.to_i }.merge(data)
rescue ActiveRecord::RecordNotFound
render json: { error: 'Assignment not found' }, status: :not_found

Comment on lines +36 to +37
rescue StandardError => e
render json: { error: e.message }, status: :internal_server_error

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not expose raw exception messages in API responses.

Line 37 returns e.message directly, which can leak internal details (SQL/error internals) to clients. Return a generic error payload and log the exception server-side.

Proposed fix
   rescue StandardError => e
-    render json: { error: e.message }, status: :internal_server_error
+    Rails.logger.error("ReportsController#fetch_response_report failed: #{e.class} - #{e.message}")
+    render json: { error: 'Internal server error' }, status: :internal_server_error
   end

Comment thread app/models/reports/review_report.rb Outdated
aanand-1706 and others added 2 commits May 26, 2026 15:39
state_key_for was not enforced — accumulate could use any key it wanted —
making it documentation-only with misleading precision. Removed it entirely:
- BaseReport#run now calls accumulate(state, row); key derivation lives in accumulate
- ReviewersReducer, ScoresReducer, AvgRangesReducer each derive their own key locally

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… fix comments

- Drop full_name from shared state and ReviewersReducer; only name is needed
- Remove ReviewResponseShared module; inline source directly in ScoresReducer
- Remove stale eager-loading note from class-level comment (now in ScoresReducer)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🚨 RSpec Tests Report

All tests passed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/models/reports/review_report.rb (1)

87-96: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Prevent orphan score buckets when reviewer metadata is missing.

On Lines 90-96, ScoresReducer can autovivify state[reviewer_id] even when ReviewersReducer skipped that reviewer (missing reviewer record). That creates report rows with nil reviewer identity fields but populated scores.

Suggested fix
       def accumulate(state, response)
+        response_map = response.response_map
+        return unless response_map
+
+        reviewer_id = response_map.reviewer_id
+        return unless reviewer_id && state.key?(reviewer_id)
+
         return if response.maximum_score.zero?
 
-        reviewer_id = response.response_map.reviewer_id
-        reviewee_id = response.response_map.reviewee_id
+        reviewee_id = response_map.reviewee_id
         round       = response.round || 1
         score_pct   = (response.calculate_total_score.to_f / response.maximum_score * 100).round(2)
 
         state[reviewer_id][:scores][reviewee_id][round] = score_pct
       end

As per coding guidelines "app/models/**/*.rb: Focus on associations, validations, callbacks, transactions, STI and polymorphic behavior, foreign keys, data integrity, and query efficiency."


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6794580d-87cc-4920-9e17-bbbeb24c8b13

📥 Commits

Reviewing files that changed from the base of the PR and between c4274fc and 2ff9d8a.

📒 Files selected for processing (2)
  • app/models/reports/base_report.rb
  • app/models/reports/review_report.rb
📜 Review details
⏰ Context from checks skipped due to timeout of 300000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: test
🧰 Additional context used
📓 Path-based instructions (1)
app/models/**/*.rb

⚙️ CodeRabbit configuration file

app/models/**/*.rb: Focus on associations, validations, callbacks, transactions, STI and polymorphic behavior,
foreign keys, data integrity, and query efficiency.

Files:

  • app/models/reports/base_report.rb
  • app/models/reports/review_report.rb
🪛 RuboCop (1.86.2)
app/models/reports/review_report.rb

[convention] 28-28: Align the keys of a hash literal if they span more than one line.

(Layout/HashAlignment)


[convention] 30-30: Align the keys of a hash literal if they span more than one line.

(Layout/HashAlignment)


[convention] 31-31: Align the keys of a hash literal if they span more than one line.

(Layout/HashAlignment)


[convention] 32-32: Align the keys of a hash literal if they span more than one line.

(Layout/HashAlignment)


[convention] 40-40: Align the keys of a hash literal if they span more than one line.

(Layout/HashAlignment)

🔇 Additional comments (2)
app/models/reports/base_report.rb (1)

19-22: LGTM!

Also applies to: 45-46, 55-56, 62-63, 72-74

app/models/reports/review_report.rb (1)

8-9: LGTM!

Also applies to: 15-15, 28-33, 40-42, 57-68, 73-83, 116-118

@efg efg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job! Please see if my suggestions help improve the code.

Comment thread app/controllers/reports_controller.rb Outdated
'teammate_review_response_map' => Reports::TeammateReviewReport,
'bookmark_rating_response_map' => Reports::BookmarkRatingReport,
'answer_tagging' => Reports::AnswerTaggingReport,
'basic' => Reports::BasicReport

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why shouldn't 'basic' be first in the list?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to the top

Comment thread app/controllers/reports_controller.rb Outdated

# Only TAs, instructors, and admins may view reports.
def action_allowed?
current_user_has_ta_privileges?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be wrong. It allows any instructor and any TA to see any report, if they know the URL.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to have current_user_teaching_staff_of_assignment?

Comment thread app/controllers/reports_controller.rb Outdated

# POST /reports/fetch_response_report
# Returns the requested report as JSON.
def fetch_response_report

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the method name "fetch_response_report" when not all of the reports are called response reports?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was referring to all reports being related to responses submitted by a user. Changed to fetch_report

type = params[:type] || 'basic'

report_class = REPORT_CLASSES[type]
unless report_class

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I question whether this clause is useful. It seems to be testing whether the app is working correctly. That kind of check should be done by automated tests, not application code within the app.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check isn't testing internal app correctness - type comes from user-supplied POST params, which means an unknown value is a realistic scenario. Without the guard, passing an invalid type would hit nil.for_assignment and raise a NoMethodError.

Comment thread app/controllers/reports_controller.rb Outdated
data = report_class.for_assignment(assignment).run
render json: { type: type, assignment_id: assignment_id.to_i }.merge(data)
rescue ActiveRecord::RecordNotFound
render json: { error: 'Assignment not found' }, status: :not_found

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar question here. Is there any way someone could invoke an operation from the UI that would fail to find an assignment?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the ideal scenario, a person shouldn't be able to invoke an operation with an invalid assignment. But since the APIs could be called directly (i.e. curl, Postman, etc.), BE should validate independently.

@reportable = reportable
end

def run

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a method comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

Comment thread app/models/reports/review_report.rb Outdated
# Reducer 3 — per-team average review score.
# Streams AssignmentTeam rows with review_mappings, responses, and scores
# eagerly loaded to avoid N+1. Delegates score computation to
# aggregate_review_grade (via ReviewAggregator concern) which picks the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should that be "ReviewReducer"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an already existing module being used in several other places as well. Should I update the module name and corresponding usages?

Comment thread app/models/response.rb Outdated
belongs_to :response_map, class_name: 'ResponseMap', foreign_key: 'map_id', inverse_of: false

scope :submitted_review_responses_for, ->(assignment_id) {
# Subquery picks the latest submitted response per (response_map, round)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there should be a method (e.g., "latest_response") that returns the latest response for a round. That would make the code clearer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The submitted_review_responses_for scope already encapsulates this logic at the SQL level using a MAX(id) subquery grouped by (response_map_id, round), which fetches the latest submitted
response per round for all response maps in a single query. Extracting a separate latest_response method would either duplicate this logic or require calling it per response map in a loop,
reintroducing N+1 queries.

Comment thread config/routes.rb Outdated
patch :grade_review
delete :delete_mapping
end
resources :review_mappings, only: [] do

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the changes in this file are whitespace only. Removing those changes, or making them in a separate commit, would make the diff easier to follow.

Comment thread config/routes.rb Outdated
resources :duties, controller: 'assignments_duties', only: [:index, :create, :destroy]
end
end
end No newline at end of file

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add an EoL.

aanand-1706 and others added 3 commits May 28, 2026 16:55
… fetch_report

- Add before_action :set_assignment to load @assignment from params[:assignment_id]
- Replace current_user_has_ta_privileges? with current_user_teaching_staff_of_assignment?
  so only instructors/TAs mapped to that specific assignment can view its reports
- Reuse @assignment in fetch_report instead of re-fetching
- Rename action/route from fetch_response_report to fetch_report — not all report
  types are response reports (e.g. basic, answer_tagging, bookmark_rating)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add explicit reducer descriptions to ReviewReport class comment
- Add run method comment with full output shape
- Fix British spellings: materialised -> materialized, normalises -> normalizes
- Fix wording: reduce-based -> reduction-based in BaseReport

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🚨 RSpec Tests Report

All tests passed.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/controllers/reports_controller.rb (1)

23-34: ⚠️ Potential issue | 🟠 Major

Add request-spec + Swagger coverage for POST /reports/fetch_report

  • Route exists: POST /reports/fetch_report in config/routes.rb (and ReportsController#fetch_report).
  • No request specs cover /reports/fetch_report.
  • swagger/v1/swagger.yaml has no fetch_report or /reports/fetch_report entry.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1d18d448-3e6a-4526-9aca-8510c1e7f8c3

📥 Commits

Reviewing files that changed from the base of the PR and between 2ff9d8a and db61e26.

📒 Files selected for processing (4)
  • app/controllers/reports_controller.rb
  • app/models/reports/base_report.rb
  • app/models/reports/review_report.rb
  • config/routes.rb
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/models/reports/base_report.rb
  • app/models/reports/review_report.rb
📜 Review details
⏰ Context from checks skipped due to timeout of 300000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: test
  • GitHub Check: test
🧰 Additional context used
📓 Path-based instructions (2)
config/routes.rb

⚙️ CodeRabbit configuration file

config/routes.rb: Flag duplicate or shadowed routes, surprising non-RESTful patterns, and route changes without matching request specs.

Files:

  • config/routes.rb
app/controllers/**/*.rb

⚙️ CodeRabbit configuration file

app/controllers/**/*.rb: Review authorization, strong parameters, HTTP status codes, and response shape consistency.
Flag N+1 queries, missing request-spec coverage, and behavior changes not reflected in Swagger docs.

Files:

  • app/controllers/reports_controller.rb
🔇 Additional comments (3)
app/controllers/reports_controller.rb (2)

35-36: Do not expose raw exception messages in API responses.

Line 36 still returns e.message directly, which can leak internal details (SQL/error internals) to clients. Log server-side and return a generic payload.

Proposed fix
   rescue StandardError => e
-    render json: { error: e.message }, status: :internal_server_error
+    Rails.logger.error("ReportsController#fetch_report failed: #{e.class} - #{e.message}")
+    render json: { error: 'Internal server error' }, status: :internal_server_error
   end

41-45: Blank/missing assignment_id returns 404 instead of 400/422.

When params[:assignment_id] is blank, Assignment.find(nil) raises RecordNotFound and yields the Line 44 404, misclassifying a malformed request as "not found".

Proposed fix
   def set_assignment
+    if params[:assignment_id].blank?
+      return render json: { error: 'assignment_id is required' }, status: :bad_request
+    end
+
     `@assignment` = Assignment.find(params[:assignment_id])
   rescue ActiveRecord::RecordNotFound
     render json: { error: 'Assignment not found' }, status: :not_found
   end
config/routes.rb (1)

197-201: LGTM!

Comment thread app/controllers/reports_controller.rb
- Fix indentation of review_mappings block
- Fix extra space on signed_up_teams member block
- Remove trailing whitespace on get :view and put '/leave' lines
- Remove trailing whitespace on collection do and end lines
- Add missing newline at end of file

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🚨 RSpec Tests Report

All tests passed.

aanand-1706 and others added 2 commits June 3, 2026 18:45
A Reducer is-not-a Report — inner reducer classes inherit streaming
scaffold behavior, not report behavior. Renaming makes the inheritance
read correctly: ReviewersReducer < BaseReducer, etc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown

🚨 RSpec Tests Report

All tests passed.

- Replace ReviewersReducer + shared state with a single AR query on
  ReviewResponseMap serialized via as_json(MAP_JSON_OPTIONS)
- MAP_JSON_OPTIONS whitelist keeps output clean (no raw FKs, timestamps, STI type)
- ScoresReducer now runs standalone with its own nested Hash.new initial_state
- AvgRangesReducer unchanged
- Output shape: { reviews: [...], scores: {...}, avg_and_ranges: {...} }

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🚨 RSpec Tests Report

All tests passed.

- Fix ScoresReducer to use aggregate_questionnaire_score instead of
  calculate_total_score (which always returned 0 due to Item#scorable?
  returning false for non-STI base class instances)
- Remove module Reports namespace from all report files and update
  controller REPORT_CLASSES references accordingly
- Add seed data for review report: questionnaire, items,
  AssignmentQuestionnaire (rounds 1 & 2), ReviewResponseMaps,
  Responses, and Answers using find_or_create_by for idempotency
- Remove unimplemented report types from REPORT_CLASSES to prevent
  NameError on controller load

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🚨 RSpec Tests Report

All tests passed.

aanand-1706 and others added 2 commits June 15, 2026 18:59
Zeitwerk maps app/models/reports/*.rb to Reports::* constants, so the
module wrapper is required. Reverts the namespace removal and restores
Reports::BasicReport and Reports::ReviewReport references in the controller.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🚨 RSpec Tests Report

All tests passed.

aanand-1706 and others added 4 commits June 15, 2026 19:19
- Add latest_submitted_response_by_round on ResponseMap: picks the most
  recent submitted response per round from already-loaded associations,
  avoiding N+1 queries
- ScoresReducer now streams ReviewResponseMap rows instead of Response
  rows, using eager-loaded responses/scores/items for all computation
- Remove hardcoded submitted_review_responses_for scope from Response
- Fix base_reducer.rb indentation inside module Reports
- Update comments to accurately reflect the new streaming approach

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Expand output schema comment with explicit types and annotations
- Correct reviewee to be AssignmentTeam id (not a Participant with user)
- Label score_pct and avg_score explicitly as percentages (0-100)
- Annotate round as a 1-based integer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removes tag_prompts, tag_prompt_deployments, and answer_tags tables
that were auto-generated from untracked migrations not belonging to
this branch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Review report seed data is local-only and not needed in the branch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🚨 RSpec Tests Report

All tests passed.

1 similar comment
@github-actions

Copy link
Copy Markdown

🚨 RSpec Tests Report

All tests passed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🚨 RSpec Tests Report

All tests passed.

`scores` was ambiguous (clashes with raw answer scores inside reviews[]).
`avg_and_ranges` was a holdover from the original Expertiza that computed
ranges — the current output is averages only, so team_averages is accurate.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🚨 RSpec Tests Report

All tests passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants