Skip to content

Quiz exercises: Fix apollon drag-and-drop quiz generation#12559

Open
FadyGergesRezk wants to merge 12 commits intodevelopfrom
bugfix/apollon/quiz-questions-not-rendering-properly
Open

Quiz exercises: Fix apollon drag-and-drop quiz generation#12559
FadyGergesRezk wants to merge 12 commits intodevelopfrom
bugfix/apollon/quiz-questions-not-rendering-properly

Conversation

@FadyGergesRezk
Copy link
Copy Markdown
Contributor

@FadyGergesRezk FadyGergesRezk commented Apr 19, 2026

Summary

This PR updates the Apollon quiz import flow so Artemis explicitly opts into Apollon quiz mode, correctly derives quiz-relevant elements from the editor model, and generates drag-and-drop questions with properly aligned backgrounds, drop locations, and drag item images.

Checklist

General

Client

  • Important: I implemented the changes with a very good performance, prevented too many (unnecessary) REST calls and made sure the UI is responsive, even with large data (e.g. using paging).
  • I strictly followed the principle of data economy for all client-server REST calls.
  • I strictly followed the client coding guidelines.
  • I strictly followed the AET UI-UX guidelines.
  • Following the theming guidelines, I specified colors only in the theming variable files and checked that the changes look consistent in both the light and the dark theme.
  • I added multiple integration tests (Vitest) related to the features (with a high test coverage), while following the test guidelines.
  • I added authorities to all new routes and checked the course groups for displaying navigation elements (links, buttons).
  • I documented the TypeScript code using JSDoc style.
  • I added multiple screenshots/screencasts of my UI changes.
  • I translated all newly inserted strings into English and German.

Motivation and Context

Generating drag-and-drop quiz questions from Apollon diagrams was unreliable in the Artemis quiz flow.

The main issues were:

  • Artemis did not explicitly opt into the new Apollon quiz mode.
  • The generate button could stay disabled even when quiz-relevant elements existed.
  • Generated drag items and drop locations could be oversized because single-element SVG exports contained extra internal padding.
  • The drop-location overlay could render before the generated background image was fully ready, leading to incorrect initial positioning.
  • Background generation needed to preserve the full diagram while removing only the selected quiz elements.

Description

This PR updates the Artemis integration with Apollon for drag-and-drop quiz generation:

  • Explicitly enables Apollon quiz mode in the quiz diagram detail flow using enableQuizMode: true.
  • Uses importDiagram(...) when loading stored Apollon diagrams in the quiz dialog.
  • Replaces the brittle quiz-relevance predicate with shared helper logic based on model.interactive, with fallback to model elements when needed.
  • Generates the quiz background from the full diagram export and blanks out selected drop areas afterward.
  • Trims per-element SVG exports before converting them to PNGs, so drag items and computed drop locations match the visible element content more closely.
  • Uses the trimmed element clip for drop-location computation.
  • Defers rendering of drag-and-drop overlays until the generated/imported background image is fully loaded.
  • Uses svgMode: 'compat' for quiz-related SVG exports to preserve correct colors in generated images.

Steps for Testing

Prerequisites:

  • 1 Instructor
  • 1 Course
  • 1 Quiz Exercise
  • 1 Apollon diagram with at least two nodes and one edge
  • Enable the ApollonQuizDragAndDrop feature toggle if required in the test environment
  1. Log in to Artemis as instructor.
  2. Navigate to a quiz exercise and open the Apollon import dialog.
  3. Open an existing Apollon diagram in the quiz detail view.
  4. Verify the editor is editable and that quiz mode controls are available in this flow.
  5. Model a diagram or use an existing one with at least one node and one edge.
  6. Mark one or more quiz-relevant elements in Apollon.
  7. Verify that Generate Quiz Exercise becomes enabled.
  8. Generate the drag-and-drop question.
  9. Verify that the generated background keeps the full diagram and removes only the selected elements.
  10. Verify that generated drag item images are tightly cropped and no longer contain large white padding.
  11. Verify that the generated drop locations align with the removed areas in the background.
  12. Open the generated drag-and-drop question in edit/preview mode.
  13. Verify that the overlay is positioned correctly after the background image loads.
  14. Verify that colors are correct in both light and dark theme.

Exam Mode Testing

Prerequisites:

  • 1 Instructor
  • 1 Exam with a Quiz Exercise containing an Apollon-generated drag-and-drop question
  1. Log in as instructor and open the exam.
  2. Start the exam as a student.
  3. Open the generated drag-and-drop question.
  4. Verify that the background image, drop locations, and drag items render correctly in exam mode as well.
  5. Verify that the UI remains usable and visually unchanged apart from the intended fix.

Review Progress

Performance Review

  • I (as a reviewer) confirm that the client changes (in particular related to REST calls and UI responsiveness) are implemented with a very good performance even for very large courses with more than 2000 students.
  • I (as a reviewer) confirm that the server changes (in particular related to database calls) are implemented with a very good performance even for very large courses with more than 2000 students.

Code Review

  • Code Review 1
  • Code Review 2

Manual Tests

  • Test 1
  • Test 2

Exam Mode Test

  • Test 1
  • Test 2

Performance Tests

  • Test 1
  • Test 2

Screenshots

  • Add screenshots or a short screencast showing:
    • Creating quiz with Apollon Drag and Drop option (intructor view)
Screenshot 2026-04-22 at 21 21 42
  • Automatically generated drop locations and options (instructor view)
Screenshot 2026-04-22 at 21 22 36 Screenshot 2026-04-22 at 21 23 14
  • Quiz participation (student view)
Screenshot 2026-04-22 at 21 28 16
  • Quiz submission (student)
Screenshot 2026-04-22 at 21 31 42

Summary by CodeRabbit

  • New Features

    • Flexible quiz/interactivity-aware element selection (explicit flags or fallback to all).
    • SVG trimming utility to extract element content and improved SVG export compatibility.
    • Editor now tracks an imported model snapshot for accurate save-state detection.
    • Background-ready flag to gate background-dependent UI and interactions.
  • Bug Fixes

    • Background generation blanks drop areas and manages image/canvas lifecycle more robustly.
    • Drop-location placement stabilized using trimmed SVG clip bounds.
    • Export and SVG generation use a more compatible mode for consistent outputs.
  • Tests

    • Added per-test canvas/image mocks with proper setup/teardown for reliable image/export tests.
  • Chores

    • Updated Apollon dependency to a newer patch version.

@FadyGergesRezk FadyGergesRezk requested review from a team as code owners April 19, 2026 19:26
@github-project-automation github-project-automation Bot moved this to Work In Progress in Artemis Development Apr 19, 2026
@github-actions github-actions Bot added client Pull requests that update TypeScript code. (Added Automatically!) modeling Pull requests that affect the corresponding module quiz Pull requests that affect the corresponding module labels Apr 19, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 19, 2026

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 quiz-aware Apollon model utilities, uses them in diagram detail and exercise-generation, introduces SVG trimming and blanked-background PNG generation for quiz exports, updates drag-and-drop editor readiness gating, and bumps the Apollon dependency.

Changes

Cohort / File(s) Summary
Quiz Model Utilities
src/main/webapp/app/modeling/shared/apollon-model.util.ts
Added optional interactive to ApollonModelData and new exports: hasExplicitInteractiveConfig(), getExplicitInteractiveElementIds(), getQuizRelevantElementIds(), hasQuizRelevantElements() to centralize quiz-relevant element detection.
SVG Rendering Helpers
src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/svg-renderer.ts
Added exported trimRenderedSVGToContent(renderedSVG) that computes content bbox, recenters children, updates viewBox/width/height, returns clip aligned to original coords, and includes robust fallbacks and DOM cleanup.
Quiz Exercise Generation
src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/quiz-exercise-generator.ts
Delegates element selection to getQuizRelevantElementIds(). Reworked diagram/background export to use svgMode: 'compat', removed global exclude approach, added createBlankedDiagramBackground(), loadImage(), canvasToBlob() to produce PNG background with white rectangles over drop locations. Per-item exports trimmed via trimRenderedSVGToContent(...); drop-location calc uses returned clip.
Diagram Detail Component
src/main/webapp/app/quiz/manage/apollon-diagrams/detail/apollon-diagram-detail.component.ts
Replaced inline interactive detection with hasQuizRelevantElements(). Added lastSavedModelJson for accurate saved/unsaved tracking using parsed/imported model via importDiagram(JSON.parse(...)); pass imported model into editor init; adjust editor options and SVG export (svgMode: 'compat').
Drag-and-Drop Question Editor (template & TS)
src/main/webapp/app/quiz/manage/drag-and-drop-question/drag-and-drop-question-edit.component.html, src/main/webapp/app/quiz/manage/drag-and-drop-question/drag-and-drop-question-edit.component.ts
Added public backgroundReady: boolean. Template gates background click-layer and drop-location rendering on backgroundReady. TS clears readiness on LOADING/ERROR or new file selection and sets it after successful sizing with change detection.
Tests / Mocks
src/main/webapp/app/quiz/manage/apollon-diagrams/detail/apollon-diagram-detail.component.spec.ts, src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/quiz-exercise-generator.spec.ts
Introduced per-test setupCanvasAndImageMocks() helpers that stub canvas and Image APIs and URL.createObjectURL/revokeObjectURL, added teardown/cleanup in test lifecycle, and updated expectations to match new export/options and ID handling.
Package Version
package.json
Bumped dependency @tumaet/apollon from 4.2.16 to 4.2.21.
sequenceDiagram
  participant UI as Quiz UI
  participant Gen as QuizExerciseGenerator
  participant Util as ApollonModelUtil
  participant Export as SVGExporter
  participant Canvas as Canvas/Image
  participant FS as File/Blob

  UI->>Gen: request generate DnD exercise (model)
  Gen->>Util: getQuizRelevantElementIds(model)
  Util-->>Gen: list of element IDs
  Gen->>Export: export full diagram SVG (svgMode: 'compat')
  Export-->>Gen: fullDiagramSVG
  Gen->>Canvas: createBlankedDiagramBackground(fullDiagramSVG, dropLocations)
  Canvas-->>Gen: backgroundPNG/blob
  loop per element
    Gen->>Export: export element SVG (svgMode:'compat', margin:0)
    Export->>Export: trimRenderedSVGToContent(svg)
    Export-->>Gen: trimmedElementSVG + clip
    Gen->>Canvas: compose item image using trimmedElementSVG and clip
    Canvas-->>Gen: item image blob
  end
  Gen->>FS: upload/save background + items
  FS-->>UI: return generated DragAndDropQuestion
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 41.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title directly addresses the main objective: fixing Apollon drag-and-drop quiz generation. It aligns with the primary changes across multiple files focused on quiz question rendering.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bugfix/apollon/quiz-questions-not-rendering-properly

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
Copy Markdown
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)
src/main/webapp/app/quiz/manage/drag-and-drop-question/drag-and-drop-question-edit.component.ts (1)

262-272: ⚠️ Potential issue | 🟠 Major

Do not mark the background ready from resize/no-image paths.

adjustClickLayerWidth() is also called by window.onresize, not only after ImageLoadingStatus.SUCCESS. Line 271 can therefore re-enable overlays while the background is missing, failed, or still zero-sized.

🐛 Proposed guard
 adjustClickLayerWidth() {
+    const backgroundImageElement = this.backgroundImage().element.nativeElement;
+    if (!this.question().backgroundFilePath || backgroundImageElement.offsetWidth === 0 || backgroundImageElement.offsetHeight === 0) {
+        this.backgroundReady = false;
+        return;
+    }
+
     // Make the background image visible upon successful image load. Initially it is set to hidden and not
     // conditionally loaded via '*ngIf' because otherwise the reference would be undefined and hence we
     // wouldn't be able to subscribe to the loading process updates.
-    this.backgroundImage().element.nativeElement.style.visibility = 'visible';
+    backgroundImageElement.style.visibility = 'visible';
 
     // Adjust the click layer to correspond to the area of the background image.
-    this.clickLayer().nativeElement.style.width = `${this.backgroundImage().element.nativeElement.offsetWidth}px`;
-    this.clickLayer().nativeElement.style.left = `${this.backgroundImage().element.nativeElement.offsetLeft}px`;
+    this.clickLayer().nativeElement.style.width = `${backgroundImageElement.offsetWidth}px`;
+    this.clickLayer().nativeElement.style.left = `${backgroundImageElement.offsetLeft}px`;
     this.backgroundReady = true;
     this.changeDetector.detectChanges();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/webapp/app/quiz/manage/drag-and-drop-question/drag-and-drop-question-edit.component.ts`
around lines 262 - 272, In adjustClickLayerWidth(), avoid marking the background
ready or making it visible on resize or when no image is loaded; first check
that the image has actually loaded and has non-zero size (e.g.,
this.backgroundImage().loadingStatus === ImageLoadingStatus.SUCCESS &&
this.backgroundImage().element.nativeElement.offsetWidth > 0) before setting
element.style.visibility = 'visible', this.backgroundReady = true, and calling
this.changeDetector.detectChanges(); otherwise only update the click layer
positioning/size (clickLayer().nativeElement.style.width/left) without toggling
backgroundReady or visibility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/webapp/app/modeling/shared/apollon-model.util.ts`:
- Around line 143-149: getQuizRelevantElementIds currently treats an empty array
from getExplicitInteractiveElementIds as “no explicit config” and falls back to
all model elements; change the logic so an explicit-but-empty interactive config
is preserved (returning an empty list) rather than defaulting to all elements.
Concretely: update getExplicitInteractiveElementIds to return a distinguishable
value (e.g., null/undefined) when no interactive configuration exists, or add a
helper like hasExplicitInteractiveConfig(model) that detects presence of
interactive.elements/interactive.relationships; then in
getQuizRelevantElementIds, check that presence and if the config exists return
the explicitInteractiveIds as-is (even if []), otherwise return
[...getModelElementIds(model)]. Ensure any callers/tests of
getExplicitInteractiveElementIds are adjusted for the new nullable/indicator
behavior.

In
`@src/main/webapp/app/quiz/manage/drag-and-drop-question/drag-and-drop-question-edit.component.html`:
- Around line 289-290: The click-layer's mousedown handler should be guarded so
clicks do nothing until the background is ready; update the template where the
element with template ref clickLayer and class "click-layer" binds
(mousedown)="backgroundMouseDown()" to only call the handler when
backgroundReady (and optionally when question().backgroundFilePath exists) —
e.g., change the binding to guard with backgroundReady before invoking
backgroundMouseDown() so drop locations cannot be created before sizing is
complete.

---

Outside diff comments:
In
`@src/main/webapp/app/quiz/manage/drag-and-drop-question/drag-and-drop-question-edit.component.ts`:
- Around line 262-272: In adjustClickLayerWidth(), avoid marking the background
ready or making it visible on resize or when no image is loaded; first check
that the image has actually loaded and has non-zero size (e.g.,
this.backgroundImage().loadingStatus === ImageLoadingStatus.SUCCESS &&
this.backgroundImage().element.nativeElement.offsetWidth > 0) before setting
element.style.visibility = 'visible', this.backgroundReady = true, and calling
this.changeDetector.detectChanges(); otherwise only update the click layer
positioning/size (clickLayer().nativeElement.style.width/left) without toggling
backgroundReady or visibility.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23d78846-5453-4fd3-a7cf-02e4c0659b6e

📥 Commits

Reviewing files that changed from the base of the PR and between e40c411 and ba57d5b.

📒 Files selected for processing (6)
  • src/main/webapp/app/modeling/shared/apollon-model.util.ts
  • src/main/webapp/app/quiz/manage/apollon-diagrams/detail/apollon-diagram-detail.component.ts
  • src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/quiz-exercise-generator.ts
  • src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/svg-renderer.ts
  • src/main/webapp/app/quiz/manage/drag-and-drop-question/drag-and-drop-question-edit.component.html
  • src/main/webapp/app/quiz/manage/drag-and-drop-question/drag-and-drop-question-edit.component.ts

Comment thread src/main/webapp/app/modeling/shared/apollon-model.util.ts
Comment on lines +289 to +290
<div #clickLayer class="click-layer" (mousedown)="backgroundMouseDown()" [ngClass]="{ disabled: !question().backgroundFilePath || !backgroundReady }">
@if (backgroundReady) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Guard the click handler while the background is not ready.

Line 289 only adds a disabled class; the handler still calls backgroundMouseDown() if pointer events are not fully blocked by CSS. Gate the call with backgroundReady so drop locations cannot be created before sizing is complete.

🐛 Proposed fix
-<div `#clickLayer` class="click-layer" (mousedown)="backgroundMouseDown()" [ngClass]="{ disabled: !question().backgroundFilePath || !backgroundReady }">
+<div `#clickLayer` class="click-layer" (mousedown)="backgroundReady && backgroundMouseDown()" [ngClass]="{ disabled: !question().backgroundFilePath || !backgroundReady }">
📝 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
<div #clickLayer class="click-layer" (mousedown)="backgroundMouseDown()" [ngClass]="{ disabled: !question().backgroundFilePath || !backgroundReady }">
@if (backgroundReady) {
<div `#clickLayer` class="click-layer" (mousedown)="backgroundReady && backgroundMouseDown()" [ngClass]="{ disabled: !question().backgroundFilePath || !backgroundReady }">
`@if` (backgroundReady) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/webapp/app/quiz/manage/drag-and-drop-question/drag-and-drop-question-edit.component.html`
around lines 289 - 290, The click-layer's mousedown handler should be guarded so
clicks do nothing until the background is ready; update the template where the
element with template ref clickLayer and class "click-layer" binds
(mousedown)="backgroundMouseDown()" to only call the handler when
backgroundReady (and optionally when question().backgroundFilePath exists) —
e.g., change the binding to guard with backgroundReady before invoking
backgroundMouseDown() so drop locations cannot be created before sizing is
complete.

@github-project-automation github-project-automation Bot moved this from Work In Progress to Ready For Review in Artemis Development Apr 19, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 19, 2026

End-to-End Test Results

Phase Status Details
Phase 1 (Relevant) ✅ Passed
TestsPassed ✅Skipped ⚠️FailedTime ⏱
Phase 1: E2E Test Report46 ran45 passed1 skipped0 failed3m 52s
Phase 2 (Remaining) ✅ Passed
TestsPassed ✅Skipped ⚠️FailedTime ⏱
Phase 2: E2E Test Report206 ran205 passed1 skipped0 failed23m 39s

Test Strategy: Two-phase execution

  • Phase 1: e2e/Login.spec.ts e2e/Logout.spec.ts e2e/SystemHealth.spec.ts e2e/exercise/modeling/ e2e/exercise/quiz-exercise/
  • Phase 2: e2e/atlas/ e2e/course/ e2e/exam/ExamAssessment.spec.ts e2e/exam/ExamChecklists.spec.ts e2e/exam/ExamCreationDeletion.spec.ts e2e/exam/ExamDateVerification.spec.ts e2e/exam/ExamManagement.spec.ts e2e/exam/ExamParticipation.spec.ts e2e/exam/ExamResults.spec.ts e2e/exam/ExamTestRun.spec.ts e2e/exam/test-exam/ e2e/exercise/ExerciseImport.spec.ts e2e/exercise/file-upload/ e2e/exercise/programming/ e2e/exercise/text/ e2e/lecture/

Overall: ✅ All E2E tests passed (both phases)

🔗 Workflow Run · 📊 Test Report

Copy link
Copy Markdown
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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/webapp/app/quiz/manage/apollon-diagrams/detail/apollon-diagram-detail.component.ts`:
- Around line 102-104: Wrap the JSON.parse + importDiagram step in a try/catch
so a corrupted diagram JSON does not throw out of the HTTP success handler:
attempt to parse diagram.jsonRepresentation and call importDiagram(...); on any
exception set model = undefined, set this.lastSavedModelJson = '' and proceed to
call this.initializeApollonEditor(model); also log or surface the parse/import
error (e.g. console.error or existing logger) so the failure is recorded instead
of breaking the detail view. Ensure you update the block that currently
references diagram.jsonRepresentation, importDiagram, this.lastSavedModelJson
and initializeApollonEditor.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7f939292-a814-496b-a109-4e7cce4ba98a

📥 Commits

Reviewing files that changed from the base of the PR and between ba57d5b and 4004957.

📒 Files selected for processing (1)
  • src/main/webapp/app/quiz/manage/apollon-diagrams/detail/apollon-diagram-detail.component.ts

Comment on lines +102 to 104
const model: UMLModel | undefined = diagram.jsonRepresentation ? importDiagram(JSON.parse(diagram.jsonRepresentation)) : undefined;
this.lastSavedModelJson = model ? JSON.stringify(model) : '';
this.initializeApollonEditor(model);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle invalid stored diagram JSON before initializing Apollon.

Line 102 can throw from JSON.parse() or importDiagram(). That exception will bypass the HTTP error callback, so a corrupted/incompatible saved diagram can break the detail view without showing the existing loading error.

Proposed guard
-                const model: UMLModel | undefined = diagram.jsonRepresentation ? importDiagram(JSON.parse(diagram.jsonRepresentation)) : undefined;
-                this.lastSavedModelJson = model ? JSON.stringify(model) : '';
-                this.initializeApollonEditor(model);
+                let model: UMLModel | undefined;
+                try {
+                    model = diagram.jsonRepresentation ? importDiagram(JSON.parse(diagram.jsonRepresentation)) : undefined;
+                } catch {
+                    this.alertService.error('artemisApp.apollonDiagram.detail.error.loading');
+                    return;
+                }
+
+                this.lastSavedModelJson = model ? JSON.stringify(model) : '';
+                this.initializeApollonEditor(model);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/webapp/app/quiz/manage/apollon-diagrams/detail/apollon-diagram-detail.component.ts`
around lines 102 - 104, Wrap the JSON.parse + importDiagram step in a try/catch
so a corrupted diagram JSON does not throw out of the HTTP success handler:
attempt to parse diagram.jsonRepresentation and call importDiagram(...); on any
exception set model = undefined, set this.lastSavedModelJson = '' and proceed to
call this.initializeApollonEditor(model); also log or surface the parse/import
error (e.g. console.error or existing logger) so the failure is recorded instead
of breaking the detail view. Ensure you update the block that currently
references diagram.jsonRepresentation, importDiagram, this.lastSavedModelJson
and initializeApollonEditor.

tamang29
tamang29 previously approved these changes Apr 20, 2026
@tamang29 tamang29 requested a review from krusche as a code owner April 20, 2026 11:56
@tamang29 tamang29 changed the title Quiz: Fix Apollon-based drag-and-drop quiz generation in Artemis. Quiz: fix apollon drag-and-drop quiz generation Apr 20, 2026
@tamang29 tamang29 changed the title Quiz: fix apollon drag-and-drop quiz generation Quiz: Fix apollon drag-and-drop quiz generation Apr 20, 2026
@tamang29 tamang29 changed the title Quiz: Fix apollon drag-and-drop quiz generation Quiz exercises: Fix apollon drag-and-drop quiz generation Apr 20, 2026
Copy link
Copy Markdown
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

🧹 Nitpick comments (1)
src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/quiz-exercise-generator.spec.ts (1)

151-155: Assert the background export call directly.

toHaveBeenCalledWith can pass on any matching export call. Since per-element exports may also use svgMode: 'compat', assert the first/background call and that it is not an include/exclude export.

🧪 Proposed assertion tightening
             await generateDragAndDropQuizExercise(course, 'Background Test', v3Model);
 
             // The new renderer exports the full model and blanks the interactive regions afterward.
-            expect(mockExportModelAsSvg).toHaveBeenCalledWith(v3Model, expect.objectContaining({ keepOriginalSize: true, svgMode: 'compat' }));
+            const backgroundExportOptions = mockExportModelAsSvg.mock.calls[0][1];
+            expect(backgroundExportOptions).toEqual(expect.objectContaining({ keepOriginalSize: true, svgMode: 'compat' }));
+            expect(backgroundExportOptions).not.toHaveProperty('include');
+            expect(backgroundExportOptions).not.toHaveProperty('exclude');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/quiz-exercise-generator.spec.ts`
around lines 151 - 155, The test currently uses toHaveBeenCalledWith which can
match any export call; instead assert the first/background export directly by
inspecting mockExportModelAsSvg.mock.calls[0] after calling
generateDragAndDropQuizExercise(course, 'Background Test', v3Model), and verify
the second argument (options) contains keepOriginalSize: true and svgMode:
'compat' and does NOT include per-element include/exclude flags (e.g., no
includeInteractiveElements or excludeInteractiveElements keys); update the
assertion in quiz-exercise-generator.spec.ts to target
mockExportModelAsSvg.mock.calls[0][1] so the background export is unambiguous.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/quiz-exercise-generator.spec.ts`:
- Around line 109-112: The module-scoped vi.fn() mockExportModelAsSvg retains
call history across tests (Vitest v4's vi.restoreAllMocks() doesn't clear
calls), causing order-dependent assertions; fix by clearing or resetting that
mock at the start of each test suite or in beforeEach (call
mockExportModelAsSvg.mockClear() or mockExportModelAsSvg.mockReset(), or
vi.clearAllMocks()) and re-spy ApollonEditor.exportModelAsSvg as needed so tests
reference only current-test calls; apply the same reset to the other occurrences
around lines 246-250 where mockExportModelAsSvg is used.

---

Nitpick comments:
In
`@src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/quiz-exercise-generator.spec.ts`:
- Around line 151-155: The test currently uses toHaveBeenCalledWith which can
match any export call; instead assert the first/background export directly by
inspecting mockExportModelAsSvg.mock.calls[0] after calling
generateDragAndDropQuizExercise(course, 'Background Test', v3Model), and verify
the second argument (options) contains keepOriginalSize: true and svgMode:
'compat' and does NOT include per-element include/exclude flags (e.g., no
includeInteractiveElements or excludeInteractiveElements keys); update the
assertion in quiz-exercise-generator.spec.ts to target
mockExportModelAsSvg.mock.calls[0][1] so the background export is unambiguous.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 09070955-ddb3-44f8-9b74-a6244015f49d

📥 Commits

Reviewing files that changed from the base of the PR and between f8e1ff3 and 6d37f83.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • src/main/webapp/app/modeling/shared/apollon-model.util.ts
  • src/main/webapp/app/quiz/manage/apollon-diagrams/detail/apollon-diagram-detail.component.spec.ts
  • src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/quiz-exercise-generator.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/webapp/app/modeling/shared/apollon-model.util.ts

Comment on lines 109 to +112
// Mock static method with proper cleanup
vi.spyOn(ApollonEditor, 'exportModelAsSvg').mockImplementation(mockExportModelAsSvg);
vi.spyOn(SVGRendererAPI, 'convertRenderedSVGToPNG').mockResolvedValue(new Blob(['PNG'], { type: 'image/png' }));
cleanupCanvasAndImageMocks = setupCanvasAndImageMocks().cleanup;
Copy link
Copy Markdown
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

🏁 Script executed:

#!/bin/bash
# Description: Find module-scoped vi.fn mocks whose call history may be inspected across tests.
rg -n -C3 --type=ts 'const\s+\w+\s*=\s*vi\.fn\(|\.mock\.calls|restoreAllMocks\(\)'

Repository: ls1intum/Artemis

Length of output: 50373


🏁 Script executed:

# Get file size first
wc -l src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/quiz-exercise-generator.spec.ts

Repository: ls1intum/Artemis

Length of output: 164


🏁 Script executed:

# Read the file to find mockExportModelAsSvg declaration and the problematic lines
cat -n src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/quiz-exercise-generator.spec.ts | head -120 | tail -40

Repository: ls1intum/Artemis

Length of output: 1854


🏁 Script executed:

# Check around lines 109-112
sed -n '100,120p' src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/quiz-exercise-generator.spec.ts

Repository: ls1intum/Artemis

Length of output: 867


🏁 Script executed:

# Check around lines 154-155
sed -n '145,160p' src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/quiz-exercise-generator.spec.ts

Repository: ls1intum/Artemis

Length of output: 894


🏁 Script executed:

# Check around lines 246-250
sed -n '240,255p' src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/quiz-exercise-generator.spec.ts

Repository: ls1intum/Artemis

Length of output: 836


🏁 Script executed:

# Find mockExportModelAsSvg declaration
rg -n 'mockExportModelAsSvg' src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/quiz-exercise-generator.spec.ts | head -20

Repository: ls1intum/Artemis

Length of output: 524


🏁 Script executed:

# Let me check the actual test structure to understand the call dependency better
sed -n '240,260p' src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/quiz-exercise-generator.spec.ts

Repository: ls1intum/Artemis

Length of output: 1061


🏁 Script executed:

# Check how many tests exist that call mockExportModelAsSvg
grep -n "it('" src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/quiz-exercise-generator.spec.ts | wc -l

Repository: ls1intum/Artemis

Length of output: 62


🏁 Script executed:

# List all tests to understand the structure
grep -n "it('" src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/quiz-exercise-generator.spec.ts

Repository: ls1intum/Artemis

Length of output: 1937


🌐 Web query:

Vitest 4 vi.restoreAllMocks() behavior clear mock history calls

💡 Result:

In Vitest 4, vi.restoreAllMocks restores original implementations only for spies created with vi.spyOn. It does not clear mock call history (mock.calls, mock.results, etc.). To clear call history without affecting implementations, use vi.clearAllMocks. To clear history and reset implementations to empty functions, use vi.resetAllMocks. This is a change from previous versions where restoreAllMocks also reset state; now it only restores vi.spyOn spies, and automocks are unaffected. Module mocks from vi.mock are hoisted and not affected by any of these; use vi.doMock/doUnmock for dynamic control.

Citations:


Reset mockExportModelAsSvg before each test to prevent order-dependent mock assertions.

mockExportModelAsSvg is a module-scoped vi.fn(), and vi.restoreAllMocks() in Vitest 4 does not clear mock call history. Tests that read mockExportModelAsSvg.mock.calls[0] can reference calls from earlier tests, making assertions order-dependent and fragile.

🧪 Proposed fix
+    const defaultExportModelAsSvgResult = {
+        svg: '<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"></svg>',
+        clip: { x: 0, y: 0, width: 100, height: 100 },
+    };
+
     beforeEach(async () => {
         await TestBed.configureTestingModule({
             providers: [
                 provideHttpClient(),
                 provideHttpClientTesting(),
@@
         }).compileComponents();
 
+        mockExportModelAsSvg.mockReset();
+        mockExportModelAsSvg.mockResolvedValue(defaultExportModelAsSvgResult);
+
         // Mock static method with proper cleanup
         vi.spyOn(ApollonEditor, 'exportModelAsSvg').mockImplementation(mockExportModelAsSvg);
         vi.spyOn(SVGRendererAPI, 'convertRenderedSVGToPNG').mockResolvedValue(new Blob(['PNG'], { type: 'image/png' }));
         cleanupCanvasAndImageMocks = setupCanvasAndImageMocks().cleanup;

Also applies to: 246-250

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/webapp/app/quiz/manage/apollon-diagrams/exercise-generation/quiz-exercise-generator.spec.ts`
around lines 109 - 112, The module-scoped vi.fn() mockExportModelAsSvg retains
call history across tests (Vitest v4's vi.restoreAllMocks() doesn't clear
calls), causing order-dependent assertions; fix by clearing or resetting that
mock at the start of each test suite or in beforeEach (call
mockExportModelAsSvg.mockClear() or mockExportModelAsSvg.mockReset(), or
vi.clearAllMocks()) and re-spy ApollonEditor.exportModelAsSvg as needed so tests
reference only current-test calls; apply the same reset to the other occurrences
around lines 246-250 where mockExportModelAsSvg is used.

@matyasht matyasht self-requested a review April 22, 2026 13:28
this.apollonDiagram.set(diagram);

const model: UMLModel = diagram.jsonRepresentation ? JSON.parse(diagram.jsonRepresentation) : undefined;
const model: UMLModel | undefined = diagram.jsonRepresentation ? importDiagram(JSON.parse(diagram.jsonRepresentation)) : undefined;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Using importDiagram() here can drop interactive class attributes and methods from legacy v3 diagrams. Because the import folds them into node.data, the new generator flow (which only checks top-level nodes and edges) can miss them, and may cause users to lose existing quiz gaps just by reopening an older diagram, right?

For that, I think we can either preserve the raw v3 model for quiz selection and generation, or update the helper methods to traverse and display those nested attribute IDs. What do you think?

Comment on lines +204 to +211
for (const dropLocation of dropLocations) {
context.fillRect(
(dropLocation.posX! / MAX_SIZE_UNIT) * canvas.width,
(dropLocation.posY! / MAX_SIZE_UNIT) * canvas.height,
(dropLocation.width! / MAX_SIZE_UNIT) * canvas.width,
(dropLocation.height! / MAX_SIZE_UNIT) * canvas.height,
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Blanking elements using fillRect() and bounding boxes may break relationship-based questions. An edge's bounding box can cover a large area, right? This may accidentally hide unrelated nodes or labels below it.

We can either use the exact SVG path to hide elements or stick with the previous method of excluding elements individually. What do you think?

dueDate: quizExercise.dueDate,
includedInOverallScore: quizExercise.includedInOverallScore,
quizQuestions: quizExercise.quizQuestions,
quizQuestions: quizExercise.quizQuestions ? convertQuizQuestionsToDTOs(quizExercise.quizQuestions) : undefined,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Using the creation converter again removes question ids and temporary DnD ids that were already there. This may cause the server to see all edited questions as new. This change resets their statistics and may cause problems in DnD mappings because saved items will drop to 0.

We might use a specific update DTO that keeps these ids, so that we can match the backend's update contract I think.

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

Labels

client Pull requests that update TypeScript code. (Added Automatically!) modeling Pull requests that affect the corresponding module quiz Pull requests that affect the corresponding module

Projects

Status: Ready For Review

Development

Successfully merging this pull request may close these issues.

4 participants