Skip to content

Conversation

@kunaljaykam
Copy link
Member

@kunaljaykam kunaljaykam commented Nov 19, 2025

Summary by CodeRabbit

  • New Features
    • Gradebook settings are now automatically copied when duplicating SCORM packages. External assessments are created in the destination gradebook for SCO items where gradebook synchronization was enabled in the source package.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 19, 2025

Walkthrough

The PR extends ScormEntityProducer to copy gradebook settings when copying SCORM packages between contexts. It adds gradebook-related imports and a new copyGradebookSettings method that iterates through SCO items in package manifests and creates external assessments in the destination using GradingService. Spring configuration is updated to inject GradingService via lookup-method into relevant beans.

Changes

Cohort / File(s) Change Summary
Gradebook settings copying implementation
scormplayer/scorm-impl/service/src/java/org/sakaiproject/scorm/service/impl/ScormEntityProducer.java, scormplayer/scorm-impl/service/src/java/org/sakaiproject/scorm/service/impl/spring-scorm-services.xml
Adds GradingService import and protected gradingService() method to ScormEntityProducer; integrates copyGradebookSettings() call in transferCopyEntities to copy gradebook settings for each SCO item when copying packages; copyGradebookSettings iterates manifest LaunchData entries and creates external assessments in destination using GradingService; Spring config adds lookup-method injections for GradingService in ScormEntityProducer and ScormApplicationService beans

Possibly related PRs

Suggested reviewers

  • ern

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically identifies the main change: retaining gradebook settings when importing SCORM packages from a site.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d18b952 and af12e6b.

📒 Files selected for processing (2)
  • scormplayer/scorm-impl/service/src/java/org/sakaiproject/scorm/service/impl/ScormEntityProducer.java (4 hunks)
  • scormplayer/scorm-impl/service/src/java/org/sakaiproject/scorm/service/impl/spring-scorm-services.xml (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-16T04:03:17.598Z
Learnt from: kunaljaykam
Repo: sakaiproject/sakai PR: 13974
File: syllabus/syllabus-impl/src/webapp/WEB-INF/components.xml:80-90
Timestamp: 2025-09-16T04:03:17.598Z
Learning: In Sakai's search infrastructure, content producers like SyllabusContentProducer don't require explicit destroy-method configuration in Spring XML. The search framework handles cleanup automatically, and adding destroy-method when no destroy() method exists would cause configuration errors.

Applied to files:

  • scormplayer/scorm-impl/service/src/java/org/sakaiproject/scorm/service/impl/spring-scorm-services.xml
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: maven-build
  • GitHub Check: sakai-deploy
  • GitHub Check: maven-build
🔇 Additional comments (3)
scormplayer/scorm-impl/service/src/java/org/sakaiproject/scorm/service/impl/ScormEntityProducer.java (2)

99-101: gradingService() lookup-method stub is appropriate but note direct instantiation

Defining a protected GradingService gradingService() that returns null is the usual pattern for Spring lookup-method injection and matches the XML wiring. Just be aware that any unit tests or callers that construct ScormEntityProducer outside of Spring will see gradingService() as null unless they subclass/override it.


183-187: Placement of copyGradebookSettings in the copy flow looks correct

Invoking copyGradebookSettings(fromContext, toContext, source, copy) after contentPackageDao.save(copy) and updateCollectionDisplayName ensures the copied package has an assigned id and title before gradebook items are created. This ordering makes sense and doesn’t change existing copy behavior except for the new gradebook side effect.

scormplayer/scorm-impl/service/src/java/org/sakaiproject/scorm/service/impl/spring-scorm-services.xml (1)

99-111: Lookup-method wiring for gradingService in ScormEntityProducer is correctly configured

The lookup-method name="gradingService" at line 102 matches the protected GradingService gradingService() method stub (line 99–101 of ScormEntityProducer.java) and aligns with the existing lookup-method pattern used for ScormApplicationServiceImpl at line 14. The bean id org.sakaiproject.grading.api.GradingService is properly registered. No issues detected.

Comment on lines +358 to +391
private void copyGradebookSettings(String fromContext, String toContext, ContentPackage source, ContentPackage copy) {
GradingService gs = gradingService();
if (gs == null) {
return;
}

ContentPackageManifest manifest = contentPackageManifestDao.load(copy.getManifestId());
if (manifest == null || manifest.getLaunchData() == null) {
return;
}

for (LaunchData launchData : manifest.getLaunchData()) {
if (!"sco".equalsIgnoreCase(launchData.getSCORMType())) {
continue;
}

String itemIdentifier = launchData.getItemIdentifier();
String sourceExternalId = source.getContentPackageId() + ":" + itemIdentifier;
String destExternalId = copy.getContentPackageId() + ":" + itemIdentifier;

// Only copy if source had gradebook sync enabled
if (!gs.isExternalAssignmentDefined(fromContext, sourceExternalId)) {
continue;
}

try {
String title = StringUtils.defaultIfBlank(copy.getTitle(), launchData.getItemTitle());
gs.addExternalAssessment(toContext, toContext, destExternalId, null, title,
100.0, copy.getDueOn(), ScormConstants.SCORM_DFLT_TOOL_NAME, null, false, null, null);
} catch (Exception e) {
log.debug("Could not copy gradebook item for SCO {}: {}", itemIdentifier, e.getMessage());
}
}
}
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

🧩 Analysis chain

Harden gradebook copy against null manifest ids and verify addExternalAssessment signature

Two points here:

  1. Null manifest id handling

copy.getManifestId() can be null (e.g., when the source had no manifest), but you call contentPackageManifestDao.load(copy.getManifestId()) without a guard. Elsewhere (copyManifest) you explicitly avoid calling load() with a null id, which suggests load(null) may throw. Recommend guarding before the DAO call:

-        ContentPackageManifest manifest = contentPackageManifestDao.load(copy.getManifestId());
+        Serializable manifestId = copy.getManifestId();
+        if (manifestId == null) {
+            return;
+        }
+
+        ContentPackageManifest manifest = contentPackageManifestDao.load(manifestId);

This keeps gradebook copying best-effort and avoids unexpected runtime failures when a package lacks a manifest.

  1. Confirm GradingService.addExternalAssessment argument ordering and values

The call

gs.addExternalAssessment(toContext, toContext, destExternalId, null, title,
        100.0, copy.getDueOn(), ScormConstants.SCORM_DFLT_TOOL_NAME, null, false, null, null);

assumes a specific 12-parameter signature and passes toContext twice, with ScormConstants.SCORM_DFLT_TOOL_NAME later in the list. Without the API in view, it’s hard to confirm that the second toContext and the position of SCORM_DFLT_TOOL_NAME align with the expected (gradebookUid, externalAppName, externalId, ...) ordering.

Please verify this against your GradingService interface and, if available, mirror the argument pattern used by other tools that create SCORM or external assessments, so points, due date, category, and release flags all line up as intended.

Also consider whether 100.0 should match the original external assignment’s points rather than a hardcoded value.

To validate both the signature and typical usage patterns in your codebase, you can run:


🏁 Script executed:

#!/bin/bash
# Inspect all usages of addExternalAssessment to confirm argument order and typical values
rg -n 'addExternalAssessment\(' -C3

# Optionally, inspect the GradingService interface to confirm the exact method signature
fd 'GradingService.java' | xargs -I{} sed -n '1,260p' "{}" | sed -n '/addExternalAssessment/,/);/p'

Length of output: 28601


Null manifest id handling needs defensive guard; addExternalAssessment signature is correct

The GradingService.addExternalAssessment call is correctly formed. Verification confirms the 12-parameter signature at gradebookng/api/src/main/java/org/sakaiproject/grading/api/GradingService.java matches your usage, and passing toContext for both gradebookUid and siteId is consistent with other tools in the codebase (assignments, other SCORM usages).

However, the null manifest id issue remains valid:

Guard copy.getManifestId() before the DAO call (lines 363-364):

-        ContentPackageManifest manifest = contentPackageManifestDao.load(copy.getManifestId());
+        Serializable manifestId = copy.getManifestId();
+        if (manifestId == null) {
+            return;
+        }
+
+        ContentPackageManifest manifest = contentPackageManifestDao.load(manifestId);

This avoids potential DAO errors when a source package has no manifest, keeping gradebook copying best-effort and defensive.

Regarding the hardcoded 100.0 points: consider whether this should inherit from the source package's original external assignment rather than using a fixed value, to preserve grading consistency across site copies.

📝 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
private void copyGradebookSettings(String fromContext, String toContext, ContentPackage source, ContentPackage copy) {
GradingService gs = gradingService();
if (gs == null) {
return;
}
ContentPackageManifest manifest = contentPackageManifestDao.load(copy.getManifestId());
if (manifest == null || manifest.getLaunchData() == null) {
return;
}
for (LaunchData launchData : manifest.getLaunchData()) {
if (!"sco".equalsIgnoreCase(launchData.getSCORMType())) {
continue;
}
String itemIdentifier = launchData.getItemIdentifier();
String sourceExternalId = source.getContentPackageId() + ":" + itemIdentifier;
String destExternalId = copy.getContentPackageId() + ":" + itemIdentifier;
// Only copy if source had gradebook sync enabled
if (!gs.isExternalAssignmentDefined(fromContext, sourceExternalId)) {
continue;
}
try {
String title = StringUtils.defaultIfBlank(copy.getTitle(), launchData.getItemTitle());
gs.addExternalAssessment(toContext, toContext, destExternalId, null, title,
100.0, copy.getDueOn(), ScormConstants.SCORM_DFLT_TOOL_NAME, null, false, null, null);
} catch (Exception e) {
log.debug("Could not copy gradebook item for SCO {}: {}", itemIdentifier, e.getMessage());
}
}
}
private void copyGradebookSettings(String fromContext, String toContext, ContentPackage source, ContentPackage copy) {
GradingService gs = gradingService();
if (gs == null) {
return;
}
Serializable manifestId = copy.getManifestId();
if (manifestId == null) {
return;
}
ContentPackageManifest manifest = contentPackageManifestDao.load(manifestId);
if (manifest == null || manifest.getLaunchData() == null) {
return;
}
for (LaunchData launchData : manifest.getLaunchData()) {
if (!"sco".equalsIgnoreCase(launchData.getSCORMType())) {
continue;
}
String itemIdentifier = launchData.getItemIdentifier();
String sourceExternalId = source.getContentPackageId() + ":" + itemIdentifier;
String destExternalId = copy.getContentPackageId() + ":" + itemIdentifier;
// Only copy if source had gradebook sync enabled
if (!gs.isExternalAssignmentDefined(fromContext, sourceExternalId)) {
continue;
}
try {
String title = StringUtils.defaultIfBlank(copy.getTitle(), launchData.getItemTitle());
gs.addExternalAssessment(toContext, toContext, destExternalId, null, title,
100.0, copy.getDueOn(), ScormConstants.SCORM_DFLT_TOOL_NAME, null, false, null, null);
} catch (Exception e) {
log.debug("Could not copy gradebook item for SCO {}: {}", itemIdentifier, e.getMessage());
}
}
}
🤖 Prompt for AI Agents
In
scormplayer/scorm-impl/service/src/java/org/sakaiproject/scorm/service/impl/ScormEntityProducer.java
around lines 358 to 391, add a defensive null-check for copy.getManifestId()
before calling contentPackageManifestDao.load(...); currently the code calls
load(copy.getManifestId()) without guarding against a null manifest id which can
cause DAO errors — if copy.getManifestId() is null, skip gradebook copying
(return or continue) to keep the process best-effort; additionally consider
replacing the hardcoded 100.0 points by retrieving the points value from the
source package’s existing external assessment (if available) and use that when
calling GradingService.addExternalAssessment.

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.

1 participant