Skip to content

Conversation

@tsarchghs
Copy link

@tsarchghs tsarchghs commented Dec 13, 2025

Summary by CodeRabbit

  • New Features

    • What-if grade calculator in the student view to preview projected course grades from hypothetical assignment scores.
    • Toggle to enable/disable what-if mode, per-assignment inputs, debounced real-time previews, and accessible controls/labels.
  • Documentation

    • Help/guidance text and validation/error messaging added.
    • French translations provided for all new what-if UI text.

✏️ Tip: You can customize this high-level summary in your review settings.

Copy link

@accesslint accesslint bot left a comment

Choose a reason for hiding this comment

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

There are accessibility issues in these changes.

<div class="row">
<div class="col-sm-12">
<h2 wicket:id="heading">Grade Report for Tony Stark</h2>
<button type="button" class="btn btn-link btn-xs gb-whatif-toggle pull-right"><wicket:message key="button.whatif.toggle" /></button>
Copy link

Choose a reason for hiding this comment

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

Looks like this element is missing an accessible name or label. That makes it hard for people using screen readers or voice control to use the control.

if ($input.length === 0) {
const starting = $row.data("grade") ? ("" + $row.data("grade")).trim()
: $row.find(".gb-summary-grade-score-raw").text().trim();
$input = $('<input type="text" class="form-control form-control-sm gb-whatif-input d-none mt-1" />');
Copy link

Choose a reason for hiding this comment

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

Looks like this element is missing an accessible name or label. That makes it hard for people using screen readers or voice control to use the control.

Copy link

@accesslint accesslint bot left a comment

Choose a reason for hiding this comment

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

There are accessibility issues in these changes.

if ($input.length === 0) {
const starting = $row.data("grade") ? ("" + $row.data("grade")).trim()
: $row.find(".gb-summary-grade-score-raw").text().trim();
$input = $('<input type="text" class="form-control form-control-sm gb-whatif-input d-none mt-1" />');
Copy link

Choose a reason for hiding this comment

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

Looks like this element is missing an accessible name or label. That makes it hard for people using screen readers or voice control to use the control.

Copy link

@accesslint accesslint bot left a comment

Choose a reason for hiding this comment

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

There are accessibility issues in these changes.

if ($input.length === 0) {
const starting = $row.data("grade") ? ("" + $row.data("grade")).trim()
: $row.find(".gb-summary-grade-score-raw").text().trim();
$input = $('<input type="text" class="form-control form-control-sm gb-whatif-input d-none mt-1" />');
Copy link

Choose a reason for hiding this comment

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

Looks like this element is missing an accessible name or label. That makes it hard for people using screen readers or voice control to use the control.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 13, 2025

Walkthrough

Adds a What‑if course grade preview feature: new API and business-service methods, grading-service implementation, UI templates and data attributes, client-side JavaScript for interactive calculation, and i18n keys; preview calculations use hypothetical scores without persisting changes.

Changes

Cohort / File(s) Summary
API & Service Layer
gradebookng/api/src/main/java/org/sakaiproject/grading/api/GradingService.java, gradebookng/impl/src/main/java/org/sakaiproject/grading/impl/GradingServiceImpl.java, gradebookng/tool/src/java/org/sakaiproject/gradebookng/business/GradebookNgBusinessService.java
Added calculateCourseGradePreview(...) API surface and implementation to compute non‑persistent course grade previews from what‑if scores; introduced convertWhatIfGrade helper and business-service delegation.
UI Templates & Panels
gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/pages/StudentPage.html, gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/GradeSummaryTablePanel.java, gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.html, gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.java
Added What‑if toggle button and scaffold (error/help/result), per‑assignment data attributes (data-assignment-id, data-points, data-grade), AJAX behavior, request handler, logging, and server-side JSON response formatting for previews.
Client-side JavaScript
gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js
Added setupWhatIfCalculator, integrated into the student view: toggle UI, per-assignment inputs, debounced POST of what‑if scores, response handling and UI updates for projected grades and errors.
Internationalization
gradebookng/bundle/src/main/bundle/gradebookng.properties, gradebookng/bundle/src/main/bundle/gradebookng_fr_FR.properties
Added i18n keys for What‑if feature: button.whatif.toggle, whatif.help, whatif.projected, whatif.error.generic, whatif.error.invalid, and whatif.label.prefix (English and French).

Possibly related PRs

Suggested reviewers

  • ern

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.29% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding a what-if calculator feature for previewing hypothetical course grades without persistence.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.java (1)

64-76: Avoid storing studentUuid (and other render-derived flags) as mutable component state for Ajax handlers.
studentUuid (Line 75-76, set at Line 118) can become stale or race under concurrent Ajax requests / reused panel instances. Also, someAssignmentsReleased is a field and should be reset at the start of onBeforeRender() now that it gates data-whatif-enabled.

Also applies to: 115-119

♻️ Duplicate comments (2)
gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/pages/StudentPage.html (1)

10-10: Update to Bootstrap 5 classes; ensure accessible name is always present.

btn-xs / pull-right are legacy; prefer btn-sm / float-end (and optionally spacing utilities). Also, if button.whatif.toggle were missing/empty, the button could end up unlabeled—worth ensuring a non-empty label or aria-label.

- <button type="button" class="btn btn-link btn-xs gb-whatif-toggle pull-right"><wicket:message key="button.whatif.toggle" /></button>
+ <button type="button" class="btn btn-link btn-sm gb-whatif-toggle float-end"><wicket:message key="button.whatif.toggle" /></button>
gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js (1)

269-389: Fix request race/cancellation (stale projected grade) + de-dupe handlers.

With debouncing alone, you can still get out-of-order responses (slow network): request A returns after request B and overwrites the newer result. Also, this.$content.on("input", ...) should be de-duped, and deactivate should cancel pending work.

 GradebookGradeSummary.prototype.setupWhatIfCalculator = function() {
+  let debounceTimer = null;
+  let lastRequestId = 0;
+  let abortController = null;
+
   const $toggle = $("body").find(".portletBody .gb-whatif-toggle");
   const callbackUrl = this.$content.attr("data-whatif-url");
   const enabled = this.$content.attr("data-whatif-enabled") === "true";
@@
-  let debounceTimer = null;
   const requestPreview = () => {
     clearTimeout(debounceTimer);
     debounceTimer = setTimeout(() => {
+      lastRequestId += 1;
+      const requestId = lastRequestId;
+      if (abortController) {
+        abortController.abort();
+      }
+      abortController = new AbortController();
+
       const payload = collectScores();
       fetch(callbackUrl, {
         method: "POST",
         headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
-        body: "whatIf=" + encodeURIComponent(JSON.stringify(payload))
+        body: "whatIf=" + encodeURIComponent(JSON.stringify(payload)),
+        signal: abortController.signal
       })
         .then(response => response.ok ? response.json() : Promise.reject())
         .then(data => {
+          if (requestId !== lastRequestId) {
+            return;
+          }
           if (data.error) {
             showError(data.error);
             $result.addClass("d-none");
             return;
           }
@@
         })
         .catch(() => {
+          if (requestId !== lastRequestId) {
+            return;
+          }
           showError(this.$content.attr("data-whatif-error") || "Unable to calculate what-if grade right now.");
         });
     }, 250);
   };
@@
-  this.$content.on("input", ".gb-whatif-input", requestPreview);
+  this.$content.off("input", ".gb-whatif-input").on("input", ".gb-whatif-input", requestPreview);
@@
   const deactivate = () => {
+    clearTimeout(debounceTimer);
+    if (abortController) {
+      abortController.abort();
+      abortController = null;
+    }
     this.$content.find(".gb-whatif-input").addClass("d-none");
     $help.addClass("d-none");
     $result.addClass("d-none");
     clearError();
   };

Separately, consider adding an aria-label to the dynamically created inputs (the accesslint theme is “missing accessible name/label”): e.g., derive from the assignment title cell text and set $input.attr("aria-label", ...).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d606549 and ee79400.

📒 Files selected for processing (10)
  • gradebookng/api/src/main/java/org/sakaiproject/grading/api/GradingService.java (1 hunks)
  • gradebookng/bundle/src/main/bundle/gradebookng.properties (2 hunks)
  • gradebookng/bundle/src/main/bundle/gradebookng_fr_FR.properties (2 hunks)
  • gradebookng/impl/src/main/java/org/sakaiproject/grading/impl/GradingServiceImpl.java (2 hunks)
  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/business/GradebookNgBusinessService.java (1 hunks)
  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/pages/StudentPage.html (1 hunks)
  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/GradeSummaryTablePanel.java (1 hunks)
  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.html (1 hunks)
  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.java (7 hunks)
  • gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js (2 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{html,jsp,vm,xhtml,xml}

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

**/*.{html,jsp,vm,xhtml,xml}: Use Bootstrap 5.2 as the preferred UI framework for styling in Sakai frontends
Leverage Bootstrap 5 components for consistent UI/UX

Files:

  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/pages/StudentPage.html
  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.html
**/*.{html,jsp,vm,xhtml,xml,css,scss}

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

Ensure all UI components work across different screen sizes (Responsive Design)

Files:

  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/pages/StudentPage.html
  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.html
**/*.{js,html,jsp,vm,xhtml,xml}

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

Use the web components in the webcomponents/ directory when possible in Sakai frontends

Files:

  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/pages/StudentPage.html
  • gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js
  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.html
**/*.{html,jsp,jspx,xml,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer kebab-case for values of HTML class and id attributes

Files:

  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/pages/StudentPage.html
  • gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js
  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.html
**/*.java

📄 CodeRabbit inference engine (.cursor/rules/logging-rule.mdc)

**/*.java: Use SLF4J parameterized logging (logger.info("Value is: {}", value)) instead of string concatenation (logger.info("Value is: " + value))
Log messages and code comments should be in English. Log messages should never be translated.

**/*.java: Java: Never use local variable type inference (var). Always declare explicit types. Yes: Map<String, Integer> counts = new HashMap<>(); No: var counts = new HashMap<String, Integer>();
When proposing Java code, spell out full types in local variable declarations, for loops, and try-with-resources
When editing Java, prefer clarity over brevity; avoid introducing language features that aren't widely used in the repo
Treat any PR or suggestion containing Java var as non-compliant. Recommend replacing with explicit types before merge

**/*.java: Use Java 17 for trunk development (Java 11 was used for Sakai 22 and Sakai 23)
Do not use local variable type inference (var) in Java code. Always declare explicit types (e.g., List<String> names = new ArrayList<>(); not var names = new ArrayList<String>();). Enforced by Checkstyle rule during mvn validate

Files:

  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.java
  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/GradeSummaryTablePanel.java
  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/business/GradebookNgBusinessService.java
  • gradebookng/api/src/main/java/org/sakaiproject/grading/api/GradingService.java
  • gradebookng/impl/src/main/java/org/sakaiproject/grading/impl/GradingServiceImpl.java
**/*.js

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

**/*.js: Use clean, standard modern JavaScript in Sakai frontends
Update jQuery code to modern JavaScript when making changes, if the changes are minimal
Prefer ES6+ features (arrow functions, template literals, destructuring, etc.) in JavaScript
Write modular, reusable JavaScript components in Sakai frontends
Minimize use of global variables and functions (Avoid Global Scope) in JavaScript

Files:

  • gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Target evergreen browsers; assume ES2022+ features and browser APIs like fetch keepalive are present; avoid legacy branches, UA sniffing, or fallbacks unless a specific evergreen gap is documented
Replace jQuery with modern DOM APIs when touching code; new work should not add jQuery dependencies
Compose Lit components, ES modules, and encapsulated helpers; keep state local and explicit with modular code
Prefer module scope or class fields; expose intentional APIs instead of incidental globals; avoid global side channels

Files:

  • gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js
**/*/*.{js,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Internal reactive state in Lit components should stay prefixed with _ and is only surfaced through getters/setters when required

Files:

  • gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js
🧠 Learnings (3)
📓 Common learnings
Learnt from: ottenhoff
Repo: sakaiproject/sakai PR: 0
File: :0-0
Timestamp: 2025-10-07T15:11:27.298Z
Learning: In samigo’s Total Scores view (samigo/samigo-app/src/webapp/jsf/evaluation/totalScores.jsp), mailto links were hidden after commit dee05746 (PR #12312, SAK-49674) added a render check requiring email.fromEmailAddress to be non-empty; PR #14154 (SAK-52058) restores visibility by checking only description.email.
📚 Learning: 2025-10-07T15:11:27.298Z
Learnt from: ottenhoff
Repo: sakaiproject/sakai PR: 0
File: :0-0
Timestamp: 2025-10-07T15:11:27.298Z
Learning: In samigo’s Total Scores view (samigo/samigo-app/src/webapp/jsf/evaluation/totalScores.jsp), mailto links were hidden after commit dee05746 (PR #12312, SAK-49674) added a render check requiring email.fromEmailAddress to be non-empty; PR #14154 (SAK-52058) restores visibility by checking only description.email.

Applied to files:

  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/pages/StudentPage.html
📚 Learning: 2025-11-24T19:12:27.663Z
Learnt from: CR
Repo: sakaiproject/sakai PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-24T19:12:27.663Z
Learning: Define custom HTML elements for Sakai-specific functionality

Applied to files:

  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.html
🧬 Code graph analysis (2)
gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js (1)
gradebookng/tool/src/webapp/scripts/gradebook-gbgrade-table.js (6)
  • assignmentId (947-947)
  • assignmentId (1220-1220)
  • assignmentId (1377-1377)
  • data (689-689)
  • data (3192-3195)
  • data (3250-3250)
gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/GradeSummaryTablePanel.java (1)
gradebookng/tool/src/webapp/scripts/gradebook-gbgrade-table.js (1)
  • assignment (3071-3071)
🔇 Additional comments (8)
gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/GradeSummaryTablePanel.java (1)

262-264: Avoid leaking unreleased grade data via data-grade / normalize attribute values.

Even if the grade is already rendered elsewhere, adding data-grade makes it trivially consumable by any script. Please double-check that, in student view, rawGrade is never populated for non-released/non-viewable items (and that excused/dropped semantics are OK to expose). Also consider string-normalizing data-points to avoid null/locale surprises.

- assignmentItem.add(new AttributeModifier("data-assignment-id", assignment.getId()));
- assignmentItem.add(new AttributeModifier("data-points", assignment.getPoints()));
- assignmentItem.add(new AttributeModifier("data-grade", StringUtils.defaultString(rawGrade)));
+ assignmentItem.add(new AttributeModifier("data-assignment-id", String.valueOf(assignment.getId())));
+ assignmentItem.add(new AttributeModifier("data-points", assignment.getPoints() == null ? "" : String.valueOf(assignment.getPoints())));
+ assignmentItem.add(new AttributeModifier("data-grade", StringUtils.defaultString(rawGrade)));
gradebookng/api/src/main/java/org/sakaiproject/grading/api/GradingService.java (1)

672-684: New API looks fine; verify all implementers/consumers are updated and contract is explicit.

Consider tightening Javadoc to specify whether whatIfScores may contain blanks/omitted ids, and whether values are interpreted as raw entry type vs display strings (e.g., percentages with/without %). Also ensure all GradingService implementers compile with this new method.

gradebookng/tool/src/java/org/sakaiproject/gradebookng/business/GradebookNgBusinessService.java (1)

574-587: Delegation is clean; verify authorization boundary (studentUuid).

Please confirm the caller (e.g., the Wicket callback handler) and/or GradingServiceImpl.calculateCourseGradePreview prevents a student from calculating a preview for someone else by passing an arbitrary studentUuid.

gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.html (1)

24-31: Nice, minimal scaffolding for help/error/result states.

gradebookng/bundle/src/main/bundle/gradebookng.properties (1)

140-141: i18n additions look good; try to avoid hardcoded English fallbacks in JS.

Ensure the UI always provides the needed data-whatif-* strings (or server JSON errors) so the client doesn’t fall back to "Unable to calculate what-if grade right now.".

Also applies to: 239-242

gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js (1)

254-258: Good integration point (student view); watch for re-init duplication.

If the student view re-initializes GradebookGradeSummary without replacing $content, make sure setupWhatIfCalculator() doesn’t accumulate event handlers.

gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.java (2)

26-54: Imports look fine for the new Ajax/JSON flow.


85-94: Ajax behavior wiring looks good.

Comment on lines +110 to 111
button.whatif.toggle=Calculateur hypoth\u00e9tique

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Keep property formatting consistent (spaces around =).

This file mostly uses key=value, but the new whatif.* entries use key = value. Consider standardizing to the existing style to reduce diff noise.

Also applies to: 205-208

🤖 Prompt for AI Agents
In gradebookng/bundle/src/main/bundle/gradebookng_fr_FR.properties around lines
110-111 (and also apply the same change to lines 205-208), the new whatif.*
entries use "key = value" spacing while the rest of the file uses "key=value";
update those lines to remove the spaces around the equal sign so they match the
existing "key=value" format (standardize formatting only, do not change keys or
translated values).

@tsarchghs tsarchghs marked this pull request as draft December 13, 2025 18:32
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.java (3)

118-118: Consider deriving studentUuid per-request for robustness.

The studentUuid is captured during onBeforeRender and stored as an instance field. While this works for the primary student view use case, the value could become stale if the panel model changes between render and an AJAX request (e.g., in instructor review modes with student navigation).

For improved robustness, derive the student UUID from the model inside handleWhatIfRequest():

 private void handleWhatIfRequest() {
     final RequestCycle requestCycle = RequestCycle.get();
     final StringValue payload = requestCycle.getRequest().getRequestParameters().getParameterValue("whatIf");
+    final Map<String, Object> modelData = (Map<String, Object>) getDefaultModelObject();
+    final String studentUuid = (String) modelData.get("studentUuid");

     // ... rest of method using local studentUuid instead of this.studentUuid

278-279: Prefer toOptionalString() for safer parameter extraction.

Using payload.toString() can return "null" as a string if the value is null. While the null checks before this line should prevent issues, using toOptionalString() is the idiomatic Wicket approach and provides additional safety.

-        if (payload != null && !payload.isNull() && StringUtils.isNotBlank(payload.toString())) {
-            final Map<String, String> raw = mapper.readValue(payload.toString(), new TypeReference<Map<String, String>>() {});
+        final String payloadStr = payload.toOptionalString();
+        if (StringUtils.isNotBlank(payloadStr)) {
+            final Map<String, String> raw = mapper.readValue(payloadStr, new TypeReference<Map<String, String>>() {});

296-297: Security concern: includeNonReleasedItems=true may expose unreleased grades to students.

Passing true for includeNonReleasedItems allows the what-if calculation to include assignments that haven't been released to students. This could inadvertently reveal information about unreleased graded items in the what-if preview, which students shouldn't see.

Consider passing false to ensure the what-if calculation only considers released assignments:

 final CourseGradeTransferBean preview = this.businessService.calculateWhatIfCourseGrade(currentGradebookUid, currentSiteId,
-        this.studentUuid, whatIfMap, true);
+        this.studentUuid, whatIfMap, false);

This ensures the preview calculation respects the same visibility rules as the normal grade display.

🧹 Nitpick comments (2)
gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js (1)

347-365: Add explicit error rejection reason for better debugging.

When the response is not OK, Promise.reject() is called without an argument, making it difficult to distinguish between network failures and server errors in the catch handler.

-        .then(response => response.ok ? response.json() : Promise.reject())
+        .then(response => response.ok ? response.json() : Promise.reject(new Error(`Server error: ${response.status}`)))
         .then(data => {
           // ...
         })
-        .catch(() => {
-          showError(this.$content.attr("data-whatif-error") || "Unable to calculate what-if grade right now.");
+        .catch(error => {
+          console.warn("What-if calculation failed:", error);
+          showError(this.$content.attr("data-whatif-error") || "Unable to calculate what-if grade right now.");
         });
gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.java (1)

273-274: Consider reusing ObjectMapper instance for better performance.

Creating a new ObjectMapper instance for each request is inefficient. ObjectMapper is thread-safe after configuration and should be reused as a static field.

 public class StudentGradeSummaryGradesPanel extends BasePanel {

     private static final long serialVersionUID = 1L;
     private static final Logger log = LoggerFactory.getLogger(StudentGradeSummaryGradesPanel.class);
+    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

     // ... in handleWhatIfRequest():
-    final ObjectMapper mapper = new ObjectMapper();
-    final ObjectNode result = mapper.createObjectNode();
+    final ObjectNode result = OBJECT_MAPPER.createObjectNode();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee79400 and 4c1afb4.

📒 Files selected for processing (5)
  • gradebookng/bundle/src/main/bundle/gradebookng.properties (2 hunks)
  • gradebookng/bundle/src/main/bundle/gradebookng_fr_FR.properties (2 hunks)
  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/pages/StudentPage.html (1 hunks)
  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.java (7 hunks)
  • gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/pages/StudentPage.html
  • gradebookng/bundle/src/main/bundle/gradebookng_fr_FR.properties
🧰 Additional context used
📓 Path-based instructions (6)
**/*.java

📄 CodeRabbit inference engine (.cursor/rules/logging-rule.mdc)

**/*.java: Use SLF4J parameterized logging (logger.info("Value is: {}", value)) instead of string concatenation (logger.info("Value is: " + value))
Log messages and code comments should be in English. Log messages should never be translated.

**/*.java: Java: Never use local variable type inference (var). Always declare explicit types. Yes: Map<String, Integer> counts = new HashMap<>(); No: var counts = new HashMap<String, Integer>();
When proposing Java code, spell out full types in local variable declarations, for loops, and try-with-resources
When editing Java, prefer clarity over brevity; avoid introducing language features that aren't widely used in the repo
Treat any PR or suggestion containing Java var as non-compliant. Recommend replacing with explicit types before merge

**/*.java: Use Java 17 for trunk development (Java 11 was used for Sakai 22 and Sakai 23)
Do not use local variable type inference (var) in Java code. Always declare explicit types (e.g., List<String> names = new ArrayList<>(); not var names = new ArrayList<String>();). Enforced by Checkstyle rule during mvn validate

Files:

  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.java
**/*.js

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

**/*.js: Use clean, standard modern JavaScript in Sakai frontends
Update jQuery code to modern JavaScript when making changes, if the changes are minimal
Prefer ES6+ features (arrow functions, template literals, destructuring, etc.) in JavaScript
Write modular, reusable JavaScript components in Sakai frontends
Minimize use of global variables and functions (Avoid Global Scope) in JavaScript

Files:

  • gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js
**/*.{js,html,jsp,vm,xhtml,xml}

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

Use the web components in the webcomponents/ directory when possible in Sakai frontends

Files:

  • gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Target evergreen browsers; assume ES2022+ features and browser APIs like fetch keepalive are present; avoid legacy branches, UA sniffing, or fallbacks unless a specific evergreen gap is documented
Replace jQuery with modern DOM APIs when touching code; new work should not add jQuery dependencies
Compose Lit components, ES modules, and encapsulated helpers; keep state local and explicit with modular code
Prefer module scope or class fields; expose intentional APIs instead of incidental globals; avoid global side channels

Files:

  • gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js
**/*/*.{js,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Internal reactive state in Lit components should stay prefixed with _ and is only surfaced through getters/setters when required

Files:

  • gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js
**/*.{html,jsp,jspx,xml,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer kebab-case for values of HTML class and id attributes

Files:

  • gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js
🧠 Learnings (1)
📓 Common learnings
Learnt from: ottenhoff
Repo: sakaiproject/sakai PR: 0
File: :0-0
Timestamp: 2025-10-07T15:11:27.298Z
Learning: In samigo’s Total Scores view (samigo/samigo-app/src/webapp/jsf/evaluation/totalScores.jsp), mailto links were hidden after commit dee05746 (PR #12312, SAK-49674) added a render check requiring email.fromEmailAddress to be non-empty; PR #14154 (SAK-52058) restores visibility by checking only description.email.
🧬 Code graph analysis (1)
gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js (1)
gradebookng/tool/src/webapp/scripts/gradebook-gbgrade-table.js (6)
  • assignmentId (947-947)
  • assignmentId (1220-1220)
  • assignmentId (1377-1377)
  • data (689-689)
  • data (3192-3195)
  • data (3250-3250)
🔇 Additional comments (5)
gradebookng/bundle/src/main/bundle/gradebookng.properties (1)

140-140: LGTM! The new i18n keys for the What-if calculator feature follow the existing naming conventions and provide clear, user-friendly messages. The whatif.label.prefix key properly supports accessibility by providing a prefix for ARIA labels on the input fields.

Also applies to: 239-243

gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js (2)

269-278: LGTM! The early return pattern correctly handles cases where the feature should be disabled: when the toggle button doesn't exist, when no callback URL is configured, or when the feature is explicitly disabled. Hiding the toggle when conditions aren't met provides a clean user experience.


383-396: LGTM! The event handling properly manages accessibility state with aria-pressed and visual state with the active class. The toggle correctly activates or deactivates the What-if calculator based on current state.

gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.java (2)

85-93: LGTM! The AJAX behavior is correctly wired in onInitialize(), and the data attributes properly expose the callback URL and configuration to the client-side JavaScript. The whatIfEnabled flag appropriately gates the feature based on having released assignments and a calculable course grade.

Also applies to: 258-263


308-324: LGTM! The response JSON construction properly handles nullable fields with individual null checks, and uses the CourseGradeFormatter for consistent course grade display formatting that matches the rest of the gradebook UI.

Copy link

@accesslint accesslint bot left a comment

Choose a reason for hiding this comment

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

There are accessibility issues in these changes.

if ($input.length === 0) {
const starting = $row.data("grade") ? ("" + $row.data("grade")).trim()
: $row.find(".gb-summary-grade-score-raw").text().trim();
$input = $('<input type="text" class="form-control form-control-sm gb-whatif-input d-none mt-1" />');
Copy link

Choose a reason for hiding this comment

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

Looks like this element is missing an accessible name or label. That makes it hard for people using screen readers or voice control to use the control.

@tsarchghs tsarchghs marked this pull request as ready for review December 13, 2025 18:37
Copy link

@accesslint accesslint bot left a comment

Choose a reason for hiding this comment

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

There are accessibility issues in these changes.

if ($input.length === 0) {
const starting = $row.data("grade") ? ("" + $row.data("grade")).trim()
: $row.find(".gb-summary-grade-score-raw").text().trim();
$input = $('<input type="text" class="form-control form-control-sm gb-whatif-input d-none mt-1" />');
Copy link

Choose a reason for hiding this comment

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

Looks like this element is missing an accessible name or label. That makes it hard for people using screen readers or voice control to use the control.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js (1)

254-266: Prevent duplicated delegated handlers when the view is re-initialized.
this.$content.on("input", ".gb-whatif-input", requestPreview); will stack if setupStudentView() runs again for the same DOM (modal reopen / partial refresh). Prefer namespaced events + explicit off() before on().

-  this.$content.on("input", ".gb-whatif-input", requestPreview);
+  this.$content.off("input.gbWhatIf", ".gb-whatif-input");
+  this.$content.on("input.gbWhatIf", ".gb-whatif-input", requestPreview);

Also consider unbinding on modal close/destroy if this component has a lifecycle hook.

Also applies to: 269-395

🧹 Nitpick comments (3)
gradebookng/impl/src/main/java/org/sakaiproject/grading/impl/GradingServiceImpl.java (1)

3509-3543: Avoid N+1 DB calls for assignment lookups in what-if preview.
Inside the whatIfScores loop, getAssignmentWithoutStatsByID(...) is called per assignment id (Line 3516), which can get expensive for large gradebooks.

Consider preloading needed assignments in bulk (e.g., fetch by ids once via gradingPersistenceManager if available, or build a map from existingRecords + one bulk fetch for missing ids).

gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.java (2)

74-75: Remove unused studentUuid field.

The AJAX behavior setup is correct. However, the studentUuid instance field (declared at line 75, assigned at line 118) is never used. The handleWhatIfRequest() method correctly derives modelStudentId from the model on each request (lines 277-278), following the past review recommendation. The instance field can be safely removed.

Apply this diff to remove the dead code:

-	private AbstractDefaultAjaxBehavior whatIfCalculationBehavior;
-	private String studentUuid;
+	private AbstractDefaultAjaxBehavior whatIfCalculationBehavior;

And remove the assignment:

 		final Map<String, Object> modelData = (Map<String, Object>) getDefaultModelObject();
 		final String userId = (String) modelData.get("studentUuid");
-		this.studentUuid = userId;
 
 		final Gradebook gradebook = getGradebook();

Also applies to: 85-93, 118-118


333-345: Error handling is functional; consider using exception types.

The error handling correctly catches and logs exceptions, with proper SLF4J parameterized logging at line 340. However, line 334 matches the exception message string "invalidGrade", which is fragile—exception messages can change, and control flow should rely on exception types or error codes instead.

If the business service can throw a more specific exception type (e.g., InvalidGradeException), consider catching that instead:

+		} catch (InvalidGradeException e) {
+			result.put("error", getString("whatif.error.invalid"));
 		} catch (IllegalArgumentException e) {
-			if ("invalidGrade".equals(e.getMessage())) {
-				result.put("error", getString("whatif.error.invalid"));
-			} else {
-				result.put("error", getString("whatif.error.generic"));
-			}
+			result.put("error", getString("whatif.error.generic"));

Alternatively, keep the current implementation if refactoring the exception hierarchy is not feasible at this time.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4c1afb4 and c04f0f8.

📒 Files selected for processing (3)
  • gradebookng/impl/src/main/java/org/sakaiproject/grading/impl/GradingServiceImpl.java (2 hunks)
  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.java (7 hunks)
  • gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js (2 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**/*.java

📄 CodeRabbit inference engine (.cursor/rules/logging-rule.mdc)

**/*.java: Use SLF4J parameterized logging (logger.info("Value is: {}", value)) instead of string concatenation (logger.info("Value is: " + value))
Log messages and code comments should be in English. Log messages should never be translated.

**/*.java: Java: Never use local variable type inference (var). Always declare explicit types. Yes: Map<String, Integer> counts = new HashMap<>(); No: var counts = new HashMap<String, Integer>();
When proposing Java code, spell out full types in local variable declarations, for loops, and try-with-resources
When editing Java, prefer clarity over brevity; avoid introducing language features that aren't widely used in the repo
Treat any PR or suggestion containing Java var as non-compliant. Recommend replacing with explicit types before merge

**/*.java: Use Java 17 for trunk development (Java 11 was used for Sakai 22 and Sakai 23)
Do not use local variable type inference (var) in Java code. Always declare explicit types (e.g., List<String> names = new ArrayList<>(); not var names = new ArrayList<String>();). Enforced by Checkstyle rule during mvn validate

Files:

  • gradebookng/impl/src/main/java/org/sakaiproject/grading/impl/GradingServiceImpl.java
  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.java
**/*.js

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

**/*.js: Use clean, standard modern JavaScript in Sakai frontends
Update jQuery code to modern JavaScript when making changes, if the changes are minimal
Prefer ES6+ features (arrow functions, template literals, destructuring, etc.) in JavaScript
Write modular, reusable JavaScript components in Sakai frontends
Minimize use of global variables and functions (Avoid Global Scope) in JavaScript

Files:

  • gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js
**/*.{js,html,jsp,vm,xhtml,xml}

📄 CodeRabbit inference engine (.cursor/rules/frontend.mdc)

Use the web components in the webcomponents/ directory when possible in Sakai frontends

Files:

  • gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Target evergreen browsers; assume ES2022+ features and browser APIs like fetch keepalive are present; avoid legacy branches, UA sniffing, or fallbacks unless a specific evergreen gap is documented
Replace jQuery with modern DOM APIs when touching code; new work should not add jQuery dependencies
Compose Lit components, ES modules, and encapsulated helpers; keep state local and explicit with modular code
Prefer module scope or class fields; expose intentional APIs instead of incidental globals; avoid global side channels

Files:

  • gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js
**/*/*.{js,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Internal reactive state in Lit components should stay prefixed with _ and is only surfaced through getters/setters when required

Files:

  • gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js
**/*.{html,jsp,jspx,xml,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer kebab-case for values of HTML class and id attributes

Files:

  • gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js
🧠 Learnings (2)
📓 Common learnings
Learnt from: ottenhoff
Repo: sakaiproject/sakai PR: 0
File: :0-0
Timestamp: 2025-10-07T15:11:27.298Z
Learning: In samigo’s Total Scores view (samigo/samigo-app/src/webapp/jsf/evaluation/totalScores.jsp), mailto links were hidden after commit dee05746 (PR #12312, SAK-49674) added a render check requiring email.fromEmailAddress to be non-empty; PR #14154 (SAK-52058) restores visibility by checking only description.email.
📚 Learning: 2025-10-07T15:11:27.298Z
Learnt from: ottenhoff
Repo: sakaiproject/sakai PR: 0
File: :0-0
Timestamp: 2025-10-07T15:11:27.298Z
Learning: In samigo’s Total Scores view (samigo/samigo-app/src/webapp/jsf/evaluation/totalScores.jsp), mailto links were hidden after commit dee05746 (PR #12312, SAK-49674) added a render check requiring email.fromEmailAddress to be non-empty; PR #14154 (SAK-52058) restores visibility by checking only description.email.

Applied to files:

  • gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.java
🔇 Additional comments (7)
gradebookng/impl/src/main/java/org/sakaiproject/grading/impl/GradingServiceImpl.java (2)

2764-2788: convertWhatIfGrade(...) conversion logic looks correct (locale-aware + letter→percent→points).
This now behaves consistently with stored “points earned” semantics and avoids non-locale parsing.


3485-3487: Inconsistent null return for permission/visibility check - verify caller context.
Both getAllCourseGrades() (line 3485-3487) and getCourseGradeForStudents() (line 3565-3581) return null when the course grade isn't displayed for non-staff users. This differs from other methods in the service that throw exceptions for similar permission failures, creating inconsistent error handling. Confirm whether callers are prepared for null returns without NPE; if these are exposed through REST/controller layer, ensure the controller converts null to a structured JSON error rather than allowing it to marshal as null or cause a 500.

gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.java (5)

26-29: LGTM: Imports and logger setup follow best practices.

The Jackson imports support JSON handling, Wicket imports enable AJAX behavior, and the SLF4J logger follows standard initialization patterns.

Also applies to: 44-46, 52-53, 64-64


258-264: LGTM: Data attributes correctly expose what-if configuration.

The attributes provide the callback URL, i18n messages, and feature enablement flag, allowing the client-side JavaScript to initialize the what-if calculator appropriately.


269-286: LGTM: Past review recommendations successfully addressed.

The method correctly:

  1. Derives modelStudentId from the model on each request (lines 277-278) rather than using a stale instance field
  2. Uses payload.toOptionalString() for safe parameter extraction (line 285)
  3. Validates the student ID before proceeding (lines 279-283)

287-302: LGTM: Robust payload parsing with proper error handling.

The logic handles null/blank payloads, invalid JSON, and non-numeric assignment IDs gracefully. The parameterized logging at line 297 follows SLF4J best practices per coding guidelines.


304-332: LGTM: Business logic correctly invoked with proper filtering.

Line 305 passes false for includeNonReleasedItems, ensuring the backend filters non-released items per the past review recommendation. The result formatting comprehensively includes all relevant grade fields.

Comment on lines +335 to +365
let debounceTimer = null;
const requestPreview = () => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
const payload = collectScores();
fetch(callbackUrl, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
body: "whatIf=" + encodeURIComponent(JSON.stringify(payload))
})
.then(response => response.ok ? response.json() : Promise.reject())
.then(data => {
if (data.error) {
showError(data.error);
$result.addClass("d-none");
return;
}
clearError();
if (data.courseGrade) {
$gradeTarget.text(data.courseGrade);
$result.removeClass("d-none");
} else {
showError(this.$content.attr("data-whatif-error") || "Unable to calculate what-if grade right now.");
$result.addClass("d-none");
}
})
.catch(() => {
showError(this.$content.attr("data-whatif-error") || "Unable to calculate what-if grade right now.");
});
}, 250);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard against out-of-order responses overwriting newer “what-if” results.
With debounce, you can still have overlapping in-flight requests (slow server + continuous typing). A late response can overwrite a newer grade/error state.

Minimal fix: track a monotonically increasing request id and ignore stale responses.

   let debounceTimer = null;
+  let requestSeq = 0;
   const requestPreview = () => {
     clearTimeout(debounceTimer);
     debounceTimer = setTimeout(() => {
+      const seq = ++requestSeq;
       const payload = collectScores();
       fetch(callbackUrl, {
         method: "POST",
         headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
         body: "whatIf=" + encodeURIComponent(JSON.stringify(payload))
       })
         .then(response => response.ok ? response.json() : Promise.reject())
         .then(data => {
+          if (seq !== requestSeq) { return; }
           if (data.error) {
             showError(data.error);
             $result.addClass("d-none");
             return;
           }
           clearError();
           if (data.courseGrade) {
             $gradeTarget.text(data.courseGrade);
             $result.removeClass("d-none");
           } else {
             showError(this.$content.attr("data-whatif-error") || "Unable to calculate what-if grade right now.");
             $result.addClass("d-none");
           }
         })
         .catch(() => {
+          if (seq !== requestSeq) { return; }
           showError(this.$content.attr("data-whatif-error") || "Unable to calculate what-if grade right now.");
         });
     }, 250);
   };

(Alternative: AbortController.)

Comment on lines +340 to +344
fetch(callbackUrl, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
body: "whatIf=" + encodeURIComponent(JSON.stringify(payload))
})
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for CSRF token patterns in the codebase
rg -n "csrf|CSRF|X-CSRF|sakai\.csrf|sakai_csrf" --type js --type html --type vm | head -100

Repository: sakaiproject/sakai

Length of output: 88


🏁 Script executed:

#!/bin/bash
# Search for data-whatif-url and whatIf usage
rg -n "data-whatif-url|whatIf=" --type js --type html | head -50

Repository: sakaiproject/sakai

Length of output: 326


🏁 Script executed:

#!/bin/bash
# Search for whatIf-related methods in gradebookng
rg -n "calculateWhatIfCourseGrade|calculateCourseGradePreview|whatIf" gradebookng --type js | head -100

Repository: sakaiproject/sakai

Length of output: 198


🏁 Script executed:

#!/bin/bash
# Look for the gradebook-grade-summary.js file and examine context around lines 340-344
fd "gradebook-grade-summary.js" --type f

Repository: sakaiproject/sakai

Length of output: 124


🏁 Script executed:

#!/bin/bash
# Read the file to see the full context around the fetch request
cat -n gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js | sed -n '260,360p'

Repository: sakaiproject/sakai

Length of output: 4342


🏁 Script executed:

#!/bin/bash
# Search for CSRF tokens without the problematic filetype
rg -n "csrf|CSRF|X-CSRF|sakai\.csrf|sakai_csrf" | head -50

Repository: sakaiproject/sakai

Length of output: 7954


🏁 Script executed:

#!/bin/bash
# Search for Sakai AJAX helpers or fetch utilities
rg -n "sakai\.[a-z]+ajax|fetch|XMLHttpRequest" gradebookng --type js | grep -i "helper\|util\|ajax" | head -30

Repository: sakaiproject/sakai

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check what's in the HTML that loads this script (look for data attributes)
fd "\.html|\.jsp|\.jspx" gradebookng | xargs grep -l "gradebook-grade-summary" | head -5

Repository: sakaiproject/sakai

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Search for the what-if endpoint in gradebookng backend
fd "\.java" gradebookng | xargs grep -l "whatif\|whatIf\|WhatIf" | head -10

Repository: sakaiproject/sakai

Length of output: 424


🏁 Script executed:

#!/bin/bash
# Search more broadly for POST handlers in gradebookng
fd "\.java" gradebookng | xargs grep -l "doPost\|RequestMapping.*POST" | head -10

Repository: sakaiproject/sakai

Length of output: 44


🏁 Script executed:

#!/antml/bash
# Look for the HTML/JSP that renders the what-if section to see if CSRF token is passed
fd "\.(html|jsp|jspx)" gradebookng | xargs grep -l "whatif\|whatIf\|data-whatif-url" | head -10

Repository: sakaiproject/sakai

Length of output: 247


🏁 Script executed:

#!/bin/bash
# Check if gradebook-grade-summary.js script tag has CSRF token nearby in HTML
fd "\.(html|jsp|jspx)" gradebookng | xargs grep -B5 -A5 "gradebook-grade-summary" | head -50

Repository: sakaiproject/sakai

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check StudentGradeSummaryGradesPanel.java for the what-if endpoint
grep -n "whatif\|whatIf\|WhatIf" gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.java

Repository: sakaiproject/sakai

Length of output: 1424


🏁 Script executed:

#!/bin/bash
# Look at StudentGradeSummaryGradesPanel.java to see how it sets up the data attribute
cat -n gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.java | head -150

Repository: sakaiproject/sakai

Length of output: 7729


🏁 Script executed:

#!/antml/bash
# Check the HTML template for what-if section
cat -n gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.html | grep -B10 -A10 "whatif\|whatIf"

Repository: sakaiproject/sakai

Length of output: 1583


🏁 Script executed:

#!/antml/bash
# Search for the actual endpoint handler (might be AJAX or REST)
rg -n "handleCalculateWhatIf|whatif|whatIf" gradebookng/tool/src/java --type java | head -30

Repository: sakaiproject/sakai

Length of output: 3448


🏁 Script executed:

#!/antml/bash
# Check if there's a CSRF token in any data attribute or if Wicket config disables CSRF
rg -n "csrf.*false|wicket.*csrf|disable.*csrf" gradebookng --type java --type html | head -20

Repository: sakaiproject/sakai

Length of output: 44


🏁 Script executed:

#!/bash
# Check the full handleWhatIfRequest method to see if there's CSRF token validation
sed -n '269,350p' gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/panels/StudentGradeSummaryGradesPanel.java

Repository: sakaiproject/sakai

Length of output: 3331


🏁 Script executed:

#!/bash
# Verify Wicket version and default CSRF handling
fd "pom.xml" gradebookng/tool | xargs grep -A5 -B5 "wicket"

Repository: sakaiproject/sakai

Length of output: 1188


🏁 Script executed:

#!/bash
# Check if there's a CsrfPreventionRequestCycleListener or CSRF validation in Wicket config
rg -n "CsrfPrevention|CSRF|csrf" gradebookng/tool/src/java --type java | grep -i "listener\|config\|application" | head -20

Repository: sakaiproject/sakai

Length of output: 44


🏁 Script executed:

#!/bash
# Look for the GradebookPage or Application class to see if CSRF is configured
fd "GradebookPage\.java|.*Application\.java" gradebookng/tool/src/java

Repository: sakaiproject/sakai

Length of output: 229


🏁 Script executed:

#!/bash
# Check the application configuration for CSRF settings
cat gradebookng/tool/src/java/org/sakaiproject/gradebookng/GradebookNgApplication.java | head -100

Repository: sakaiproject/sakai

Length of output: 3776


🏁 Script executed:

#!/bash
# Also check GradebookPage.java for any CSRF configuration
rg -n "csrf|CSRF|AbstractDefaultAjaxBehavior" gradebookng/tool/src/java/org/sakaiproject/gradebookng/tool/pages/GradebookPage.java

Repository: sakaiproject/sakai

Length of output: 206


This fetch() call will fail in production due to missing CSRF token.

Wicket's AbstractDefaultAjaxBehavior (used here) enforces CSRF protection by default. The endpoint expects requests to include Wicket's CSRF token (either as a request parameter or header), but the raw fetch() call bypasses Wicket's automatic token injection.

Either:

  • Use Wicket's AJAX mechanism instead of raw fetch(), or
  • Extract and include the CSRF token from the page in the request headers/body
🤖 Prompt for AI Agents
In gradebookng/tool/src/webapp/scripts/gradebook-grade-summary.js around lines
340-344, the raw fetch() call omits Wicket's CSRF token so the POST will be
rejected in production; fix by reading the token already rendered on the page
(e.g., the Wicket CSRF token stored in a meta tag or hidden input) and include
it with the request — either append it as a form parameter (e.g., add
&wicket:token=... to the body) or set it as the expected header before calling
fetch(), or switch to using Wicket's AJAX helper so the framework injects the
token automatically.

@adrianfish
Copy link
Contributor

This sounds like a cool feature. I'd like to see #14290 merged before this though, personally. The letter grading PR has been in for ages, way before the feature freeze date and I can't afford the time to rework it around these changes if they go in first. I'm employed part time on Sakai now so I just wouldn't have the time to update my PR again.

@ern ern changed the title SAK-52177 "What if" calculator for grades SAK-52177 Gradebook preview grading calculator for hypothetical grades Dec 17, 2025
@ern ern removed the CLA Required label Jan 6, 2026
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.

3 participants