Skip to content

Conversation

@FelixTJDietrich
Copy link
Collaborator

@FelixTJDietrich FelixTJDietrich commented Nov 28, 2025

Checklist

General

Client

Motivation and Context

Unsafe types often cause bugs.

Description

This pull request introduces several improvements focused on type safety and robustness across various components, particularly in handling different participation and notification types. The main changes include using type guard functions instead of type assertions, refining notification parsing logic, and improving null/undefined checks for safer code execution.

Type Safety and Participation Handling

  • Replaced direct type assertions with type guard functions like isStudentParticipation and isProgrammingExerciseStudentParticipation throughout the codebase, ensuring that operations on participations are only performed when the type is correct. This affects files such as exercise-scores.component.ts, list-of-complaints.component.ts, and exercise-scores-export-button.component.ts. [1] [2] [3] [4] [5] [6] [7] [8] [9]
  • Updated the logic for exporting exercise scores to filter and process only valid StudentParticipation objects, preventing errors from invalid types and improving data integrity. [1] [2]

Notification Parsing Improvements

  • Added dedicated parsing methods (parseCategory and parseViewingStatus) in course-notification-websocket.service.ts for notification categories and statuses, ensuring only valid enum values are used and preventing runtime errors from unexpected values. [1] [2]

Null/Undefined Handling

  • Improved null/undefined checks in several components, such as handling missing answer posts in posting-reactions-bar.component.ts and channel names in forwarded-message.component.ts, to prevent runtime errors and ensure graceful fallback. [1] [2]

Minor Type and API Updates

  • Updated type definitions from as assertions to satisfies for better compile-time type checking (e.g., in programming-exam-submission.component.ts).
  • Removed unused imports and streamlined import statements for clarity and maintainability across several files. [1] [2] [3] [4] [5] [6] [7]

API Usage Update

  • Updated usage of the Intl.supportedValuesOf API to remove unnecessary type assertions, reflecting modern TypeScript standards.

Steps for Testing

TODO, Artemis client should still behave the same

Testserver States

You can manage test servers using Helios. Check environment statuses in the environment list. To deploy to a test server, go to the CI/CD page, find your PR or branch, and trigger the deployment.

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

Test Coverage

Unchanged

Screenshots

Unchanged

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced validation for participation types to prevent invalid operations and provide clearer error handling.
    • Improved robustness of CSV export by adding safer participation type checks and validation.
    • Strengthened date parsing for submission time calculations to handle multiple date formats.
  • Refactor

    • Replaced direct type casting with runtime type guards throughout the codebase for better type safety and error prevention.
    • Introduced utility functions for participation type validation to standardize type-checking patterns.

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

@FelixTJDietrich FelixTJDietrich requested review from a team as code owners November 28, 2025 11:35
@github-project-automation github-project-automation bot moved this to Work In Progress in Artemis Development Nov 28, 2025
@github-actions github-actions bot added client Pull requests that update TypeScript code. (Added Automatically!) assessment Pull requests that affect the corresponding module communication Pull requests that affect the corresponding module core Pull requests that affect the corresponding module exam Pull requests that affect the corresponding module exercise Pull requests that affect the corresponding module fileupload Pull requests that affect the corresponding module iris Pull requests that affect the corresponding module lecture Pull requests that affect the corresponding module modeling Pull requests that affect the corresponding module programming Pull requests that affect the corresponding module quiz Pull requests that affect the corresponding module text Pull requests that affect the corresponding module labels Nov 28, 2025
@FelixTJDietrich FelixTJDietrich changed the title Development: Improve client type-safety Development: Improve TypeScript type-safety Nov 28, 2025
@FelixTJDietrich FelixTJDietrich changed the title Development: Improve TypeScript type-safety Development: Improve TypeScript type-safety around participations and more Nov 28, 2025
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 379 to 383
// Also remove the results, as they can have circular structures as well and don't have to be saved here.
if (copy.templateParticipation) {
copy.templateParticipation = _omit(copy.templateParticipation, ['exercise', 'results']) as TemplateProgrammingExerciseParticipation;
if (isTemplateProgrammingExerciseParticipation(copy.templateParticipation)) {
const { exercise: _ignoredExercise, ...rest } = copy.templateParticipation;
copy.templateParticipation = rest;
}

Choose a reason for hiding this comment

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

P1 Badge Restore removal of results before sending programming exercise

The new convertDataFromClient only strips the exercise field from template/solution participations, leaving their results intact despite the comment that these should be removed. Those result objects carry submissions/participations with back-references to the exercise, so sending an updated programming exercise can now include circular structures or a large results payload that the previous _omit(..., ['exercise', 'results']) avoided, potentially breaking save/update calls when template or solution participations contain results.

Useful? React with 👍 / 👎.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 28, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Systematic refactoring to replace unsafe type casts with runtime type guards throughout the codebase. Introduces type-guard utility functions and validation patterns to safely handle participation types, enum parsing, and property access. Expands enum states for programming submissions and improves type annotations across multiple component files.

Changes

Cohort / File(s) Change Summary
Core Type Guard Utilities
src/main/webapp/app/exercise/result/result.utils.ts, src/main/webapp/app/programming/shared/utils/programming-exercise.utils.ts
Updated isStudentParticipation to accept optional Participation with type guard predicate. Added new exported type guards: isSolutionProgrammingExerciseParticipation and isTemplateProgrammingExerciseParticipation, each accepting Participation | undefined.
Programming Exercise Components
src/main/webapp/app/programming/manage/code-editor/instructor-and-editor-container/code-editor-instructor-base-container.component.ts, src/main/webapp/app/programming/manage/services/programming-exercise-sharing.service.ts, src/main/webapp/app/programming/manage/services/programming-exercise.service.ts, src/main/webapp/app/programming/shared/commit-history/commit-history.component.ts
Replaced direct type casts with type-guard checks for template, solution, and student programming exercise participations. Updated participation destructuring and assignment logic to use guards instead of omit-based property removal.
Programming Submissions & State
src/main/webapp/app/programming/shared/services/programming-submission.service.ts
Expanded ProgrammingSubmissionState enum with new state flags (HAS_NO_PENDING_SUBMISSION, IS_BUILDING_PENDING_SUBMISSION, HAS_FAILED_SUBMISSION, IS_QUEUED). Added defensive date parsing helper and participation type guards for submission initialization.
Exercise Scores & Export
src/main/webapp/app/exercise/exercise-scores/exercise-scores.component.ts, src/main/webapp/app/exercise/exercise-scores/export-button/exercise-scores-export-button.component.ts
Replaced StudentParticipation casts with isStudentParticipation and isProgrammingExerciseStudentParticipation guards. Updated ExerciseScoresRow type from any to `Record<string, string
Assessment & Participation Models
src/main/webapp/app/assessment/manage/list-of-complaints/list-of-complaints.component.ts, src/main/webapp/app/exercise/shared/entities/participation/participation.model.ts, src/main/webapp/app/exercise/shared/entities/result/result.model.ts, src/main/webapp/app/exercise/result/result.service.ts
Introduced guarded type checks for participation type derivation instead of direct casts. Updated isPracticeResult to use guard pattern. Removed direct StudentParticipation imports where guards provide safer alternatives.
Exercise Participation Components
src/main/webapp/app/exercise/participation/participation.component.ts, src/main/webapp/app/exercise/participation/participation.service.ts, src/main/webapp/app/exercise/participation-submission/participation-submission.component.ts
Updated getRepositoryLink method signature to accept generic Participation with guard-based type checking. Refactored participation list merging with explicit early-exit handling. Replaced StudentParticipation casts with isStudentParticipation guard usage.
Submission Components (File Upload, Text, Modeling)
src/main/webapp/app/fileupload/manage/assess/file-upload-assessment.component.ts, src/main/webapp/app/fileupload/overview/file-upload-submission/file-upload-submission.component.ts, src/main/webapp/app/text/overview/text-editor/text-editor.component.ts, src/main/webapp/app/modeling/overview/modeling-submission/modeling-submission.component.ts, src/main/webapp/app/modeling/manage/assess/modeling-assessment-editor/modeling-assessment-editor.component.ts
Added private helper requireStudentParticipation() to enforce StudentParticipation type with error throwing. Replaced direct casts throughout component lifecycle methods. Updated submission reconnection logic to use validated participation objects.
Quiz & Communication Components
src/main/webapp/app/quiz/overview/participation/quiz-participation.component.ts, src/main/webapp/app/communication/course-notification/course-notification-websocket.service.ts, src/main/webapp/app/communication/forwarded-message/forwarded-message.component.ts, src/main/webapp/app/communication/posting-reactions-bar/posting-reactions-bar.component.ts
Added parsing helpers for notification enums with early-exit guards. Refined channel-name extraction for forwarded messages with type checking. Guarded StudentParticipation access in quiz participation. Refactored getShowNewMessageIcon to avoid repeated calls and undefined handling.
Chart & Data Components
src/main/webapp/app/programming/manage/grading/charts/sca-category-distribution-chart.component.ts, src/main/webapp/app/programming/manage/grading/charts/test-case-distribution-chart.component.ts, src/main/webapp/app/shared/data-table/data-table.component.ts
Added explicit type annotations for chart data entries (NgxChartsMultiSeriesDataEntry, SeriesEntry). Removed implicit any[] casts in chart initialization. Narrowed paging value type inference in data-table cache retrieval.
Type Annotation Improvements
src/main/webapp/app/core/course/manage/update/course-update.component.ts, src/main/webapp/app/exam/overview/exercises/programming/programming-exam-submission.component.ts, src/main/webapp/app/iris/overview/services/iris-chat.service.ts, src/main/webapp/app/lecture/manage/lecture-attachments/lecture-attachments.component.ts, src/main/webapp/app/programming/manage/edit-selected/programming-exercise-edit-selected.component.ts, src/main/webapp/app/programming/manage/update/programming-exercise-update.component.ts, src/main/webapp/app/quiz/manage/update/quiz-exercise-update.component.ts
Replaced as any casts with explicit types (Record<string, unknown>, satisfies). Removed unnecessary type assertions while maintaining functional equivalence. Narrowed any casts for mode/chatMode access.
Exercise & Service Updates
src/main/webapp/app/exercise/services/exercise.service.ts, src/main/webapp/app/programming/shared/code-editor/services/code-editor-submission.service.ts
Updated category parsing to accept string or object with satisfies ExerciseCategory[]. Shifted nullish coalescing for exercise derivation. Updated parseViewingStatus implementation.
Navigation & Fullscreen Utilities
src/main/webapp/app/shared/util/navigate-back.util.ts, src/main/webapp/app/shared/util/fullscreen.util.ts
Added guard-based teamId derivation for team-mode navigation. Added explicit type declarations (FullscreenDocument, FullscreenElement) for browser-specific fullscreen properties.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Key areas requiring attention:
    • ProgrammingSubmissionState enum expansion — verify new state transitions are semantically correct and don't break existing subscribers
    • ExerciseScoresRow type change from any to Record<string, string | number | boolean | undefined> — confirm all row builders comply with the stricter type
    • Multiple requireStudentParticipation() helper implementations across submission components — ensure error throwing and recovery paths are consistent
    • Participation type guard introduction in service layer (programming-exercise-sharing.service.ts, programming-exercise.service.ts) — verify participation object mutations still work correctly after destructuring changes
    • Date parsing helper in programming-submission.service.ts — validate ETA calculations work with new format support (Dayjs, Date, string)

Suggested labels

client, ready for review, programming, exercise, quiz

Suggested reviewers

  • maximiliansoelch

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly describes the main objective: improving TypeScript type-safety around participations, which aligns with the comprehensive changes made across the codebase.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/development/improve-client-typesafety

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

🧹 Nitpick comments (12)
src/main/webapp/app/programming/manage/edit-selected/programming-exercise-edit-selected.component.ts (1)

67-74: Stronger typing for requestOptions looks good; a dedicated options type could be a future improvement

Changing requestOptions from an untyped/any-like object to Record<string, unknown> improves type-safety while preserving behaviour. If ProgrammingExerciseService.updateTimeline expects a stable, well-defined options shape (e.g. only notificationText and maybe a few others), you might later consider introducing a small interface/type (e.g. ProgrammingExerciseTimelineOptions) instead of a generic Record, but that’s optional and not required for this PR.

Based on learnings, this aligns with the repo’s preference to avoid unsafe type assertions.

src/main/webapp/app/communication/posting-reactions-bar/posting-reactions-bar.component.ts (1)

222-247: Refactor to call signal only once and eliminate redundant null check.

The current implementation still calls sortedAnswerPosts() twice (lines 223 and 228), which doesn't achieve the optimization goal. Additionally, the null check at lines 229-231 is redundant because if line 223's check passes, sortedAnswerPosts() is truthy.

Apply this diff to call the signal once with a single guard:

 getShowNewMessageIcon(): boolean {
+    const answers = this.sortedAnswerPosts();
-    if (!this.sortedAnswerPosts()) {
+    if (!answers) {
         return false;
     }
 
     let showIcon = false;
-    const answers = this.sortedAnswerPosts();
-    if (!answers) {
-        return false;
-    }
     // iterate over all answer posts
     answers.forEach((answerPost: Posting) => {
         // check if the answer post is newer than the last read date
src/main/webapp/app/iris/overview/services/iris-chat.service.ts (1)

335-347: Type assertion is safe here; consider a small type guard for even stronger typing

The change from as any to as { mode?: ChatServiceMode } is a clear improvement and keeps behavior identical while tightening the type surface. If you want to push type‑safety a bit further and remove the remaining assertion, you could introduce a narrow type guard around mode and reuse it here, e.g.:

function hasChatMode(session: IrisSession): session is IrisSession & { mode?: ChatServiceMode } {
    return 'mode' in session;
}

// ...
const chatMode = newIrisSession.chatMode ?? (hasChatMode(newIrisSession) ? newIrisSession.mode : undefined) ?? ChatServiceMode.COURSE;

This keeps the runtime behavior the same, but fully aligns with the “avoid assertions, use guards” direction of the PR. Based on learnings, this matches the preference for type guards over assertions in the TypeScript codebase.

src/main/webapp/app/shared/data-table/data-table.component.ts (1)

270-276: Eliminate remaining as PagingValue by constructing a properly narrowed value

The logic is correct and safe, but you can avoid the remaining type assertion by explicitly constructing a PagingValue from the cached string. This keeps behavior while matching the PR’s goal of reducing assertions.

     /**
      * Get "items per page" setting from local storage. If it does not exist, use the default.
      */
     private getCachedEntitiesPerPage = () => {
         const cachedValue = this.localStorageService.retrieve<string>(this.perPageCacheKey);
         if (cachedValue) {
-            const parsedValue = parseInt(cachedValue, 10) || cachedValue;
-            if (this.PAGING_VALUES.includes(parsedValue as PagingValue)) {
-                return parsedValue as PagingValue;
-            }
+            const parsedValue: PagingValue = cachedValue === 'all' ? 'all' : parseInt(cachedValue, 10);
+            if (this.PAGING_VALUES.includes(parsedValue)) {
+                return parsedValue;
+            }
         }
         return this.DEFAULT_PAGING_VALUE;
     };

This keeps the runtime behavior (only values in PAGING_VALUES are returned, everything else falls back to the default) while relying on type narrowing instead of assertions. Based on learnings, prefer guards/narrowing over as where feasible.

src/main/webapp/app/exercise/services/exercise.service.ts (2)

382-389: Consider adding shape validation for parsed category objects.

The parsing logic correctly handles both string and object inputs and uses satisfies appropriately. However, there's no validation that categoryObj has the expected category and color properties before passing them to the ExerciseCategory constructor.

If the server sends malformed data, this could silently create invalid ExerciseCategory instances with undefined values.

Apply this diff to add defensive validation:

     static parseExerciseCategories(exercise?: Exercise) {
         if (exercise?.categories) {
             exercise.categories = exercise.categories.map((category) => {
                 const categoryObj = typeof category === 'string' ? JSON.parse(category) : category;
+                if (!categoryObj || typeof categoryObj.category !== 'string' || typeof categoryObj.color !== 'string') {
+                    console.warn('Invalid exercise category format:', categoryObj);
+                    return new ExerciseCategory('', '');
+                }
                 return new ExerciseCategory(categoryObj.category, categoryObj.color);
             }) satisfies ExerciseCategory[];
         }
     }

395-400: Consider adding shape validation for consistency.

Similar to parseExerciseCategories, this function would benefit from validating that the parsed JSON has the expected shape before constructing ExerciseCategory instances.

Apply this diff to add defensive validation:

     convertExerciseCategoriesAsStringFromServer(categories: string[]): ExerciseCategory[] {
         return categories.map((category) => {
             const parsed = JSON.parse(category) as { category: string; color: string };
+            if (!parsed || typeof parsed.category !== 'string' || typeof parsed.color !== 'string') {
+                console.warn('Invalid exercise category format:', parsed);
+                return new ExerciseCategory('', '');
+            }
             return new ExerciseCategory(parsed.category, parsed.color);
         });
     }
src/main/webapp/app/shared/util/fullscreen.util.ts (2)

21-32: Use the correct IE/Edge exit API (msExitFullscreen) instead of msRequestFullscreen on Document

msRequestFullscreen is an element-level API and you already model it on FullscreenElement, but here it’s declared and invoked on FullscreenDocument/Document. The document-level exit method is typically msExitFullscreen, so this branch is likely dead on IE/old Edge and slightly misrepresents the API in the type.

You can make this clearer and more correct with a small rename:

 export function exitFullscreen() {
-    const docElement = document as FullscreenDocument;
+    const docElement = document as FullscreenDocument;
@@
-    } else if (docElement.msRequestFullscreen) {
-        docElement.msRequestFullscreen();
+    } else if (docElement.msExitFullscreen) {
+        docElement.msExitFullscreen();
     } else if (docElement.webkitExitFullscreen) {
         docElement.webkitExitFullscreen();
     }
 }
@@
 type FullscreenDocument = Document & {
     webkitFullscreenElement?: Element | null;
     mozFullScreenElement?: Element | null;
     msFullscreenElement?: Element | null;
     webkitExitFullscreen?: () => void;
     mozCancelFullScreen?: () => void;
-    msRequestFullscreen?: () => void;
+    msExitFullscreen?: () => void;
 };

This mainly affects very old browsers but improves correctness and keeps the custom types aligned with the actual platform API.

Also applies to: 52-59


38-49: Consider tightening enterFullscreen’s parameter type to leverage FullscreenElement

enterFullscreen still accepts element: any and then assigns it to a FullscreenElement, which effectively reintroduces an unchecked cast at the call sites. Since you now have a dedicated FullscreenElement type, you could expose that (or at least Element) directly in the signature:

-export function enterFullscreen(element: any) {
-    const target: FullscreenElement = element;
+export function enterFullscreen(element: FullscreenElement) {
+    const target = element;

This pushes type-safety to the callers and avoids any while still supporting the vendor-prefixed methods defined on FullscreenElement. If updating all call sites is too invasive right now, even element: Element would be a step forward. Based on learnings, this would further reduce reliance on any.

Also applies to: 61-65

src/main/webapp/app/programming/shared/commit-history/commit-history.component.ts (1)

11-17: Type-guarded participation selection looks correct; consider explicit fallback

The use of isTemplateProgrammingExerciseParticipation / isSolutionProgrammingExerciseParticipation / isProgrammingExerciseStudentParticipation correctly narrows the participation types before accessing programming-specific fields and wiring programmingExercise.

One minor robustness tweak you might consider (non-blocking): in loadStudentParticipation, if the guard ever fails, this.participation stays undefined but handleCommits() still runs and will later access this.participation.id. An early return or explicit error/log in that case would fail faster and more clearly, instead of relying on downstream null errors.

Also applies to: 70-85, 99-115

src/main/webapp/app/exercise/exercise-scores/exercise-scores.component.ts (1)

323-341: Safer handling of student vs non-student participations in exports and search

exportNames, searchParticipationFormatter, and searchTextFromParticipation now all use isStudentParticipation before touching student-specific properties, which prevents misusing template/solution participations and matches the new participation typing.

One small, non-blocking edge case: exportNames still uses the index === 0 check to emit the team header. If participations[0] were ever a non-student participation, the header would never be written even though later rows might be teams. If you ever expect mixed participation arrays here, consider basing the header condition on whether any data rows have been pushed yet instead of the original index.

Also applies to: 350-372

src/main/webapp/app/communication/course-notification/course-notification-websocket.service.ts (1)

94-98: Silent drop of notifications with invalid category/status may hinder debugging

Using parseCategory/parseViewingStatus and early‑return is a good defensive pattern, but right now invalid payloads are dropped without any logging or metrics. If the backend or WS payloads ever regress, this will be hard to diagnose.

Consider at least a debug‑level log or a counter for rejected notifications so issues can be detected and traced without affecting normal behavior.

Also applies to: 103-104

src/main/webapp/app/programming/manage/grading/charts/test-case-distribution-chart.component.ts (1)

110-123: Local SeriesEntry type improves safety; consider tightening the output emitter type

The introduction of the SeriesEntry type and explicitly typed weight, weightAndBonus, and points objects removes the need for loose any usage while still being structurally compatible with NgxChartsMultiSeriesDataEntry. The push/update logic for relWeight, relScore, and relPoints remains unchanged and type-safe.

As a small follow-up to these type improvements, you could also narrow the output emitter to use the existing TestCaseColors alias instead of any:

-    @Output() testCaseColorsChange = new EventEmitter<any>();
+    @Output() testCaseColorsChange = new EventEmitter<TestCaseColors>();

The emitted object already matches that shape, so this should be a pure type-safety win.
Based on learnings, this further reduces any without adding runtime checks.

Also applies to: 104-147

Comment on lines +134 to +152
private parseCategory(category: unknown): CourseNotificationCategory | undefined {
if (typeof category === 'number' && CourseNotificationCategory[category] !== undefined) {
return category as CourseNotificationCategory;
}
if (typeof category === 'string' && category in CourseNotificationCategory) {
return CourseNotificationCategory[category as keyof typeof CourseNotificationCategory];
}
return undefined;
}

private parseViewingStatus(status: unknown): CourseNotificationViewingStatus | undefined {
if (typeof status === 'number' && CourseNotificationViewingStatus[status] !== undefined) {
return status as CourseNotificationViewingStatus;
}
if (typeof status === 'string' && status in CourseNotificationViewingStatus) {
return CourseNotificationViewingStatus[status as keyof typeof CourseNotificationViewingStatus];
}
return undefined;
}
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

🧩 Analysis chain

🌐 Web query:

TypeScript numeric enum runtime behavior string keys "0" index access

💡 Result:

Short answer: numeric enums emit both forward (name→number) and reverse (number→name) mappings at runtime, so the compiled enum object has string keys like "0", "1", ... in addition to the member names. That means enum[0] and enum["0"] both look up the same runtime property (the reverse mapping) and return the member name; string enums do not emit reverse mappings. [1][2]

Quick examples / notes:

  • TypeScript handbook: numeric enums produce real objects with reverse mapping (e.g. E[0] === "X"). String enums only emit forward mappings. [1]
  • Practical consequence: Object.keys(MyNumericEnum) will include numeric-string keys ("0","1",...) first (the reverse mapping), then the names; you can index the enum with either the numeric index or its string form at runtime. [2]

Sources:
[1] TypeScript Handbook — Enums.
[2] LogRocket / articles explaining reverse mappings and Object.keys behavior.


🏁 Script executed:

#!/bin/bash
# Find the file under review
fd "course-notification-websocket.service.ts" -type f

# Also find the enum definitions
fd -e ts -type f | xargs rg -l "CourseNotificationCategory|CourseNotificationViewingStatus" | head -20

Repository: ls1intum/Artemis

Length of output: 2628


🏁 Script executed:

#!/bin/bash
# Read the enum definition file
cat -n src/main/webapp/app/communication/shared/entities/course-notification/course-notification-viewing-status.ts

echo "---"

# Also check for the category enum (might be in the main course-notification.ts)
cat -n src/main/webapp/app/communication/shared/entities/course-notification/course-notification.ts | head -100

Repository: ls1intum/Artemis

Length of output: 2227


🏁 Script executed:

#!/bin/bash
# Find CourseNotificationCategory enum
fd "course-notification-category" -type f

echo "---"

# Read the service file around lines 134-152
cat -n src/main/webapp/app/communication/course-notification/course-notification-websocket.service.ts | sed -n '130,160p'

Repository: ls1intum/Artemis

Length of output: 1440


🏁 Script executed:

#!/bin/bash
# Find CourseNotificationCategory enum
find . -name "course-notification-category*" -type f

echo "---"

# Also search with rg in case it's defined differently
rg "enum CourseNotificationCategory" -A 5

Repository: ls1intum/Artemis

Length of output: 1056


Fix numeric-like strings in enum parsing to maintain correct runtime types

The review comment correctly identifies a runtime type safety issue. TypeScript numeric enums produce reverse mappings at runtime, so both CourseNotificationCategory and CourseNotificationViewingStatus have string keys like "0", "1", etc. that map back to the enum member names.

When a numeric-like string such as "0" is passed:

  • The check "0" in CourseNotificationCategory returns true (reverse mapping exists)
  • CourseNotificationCategory["0"] returns "GENERAL" (a string)
  • But the return type is CourseNotificationCategory | undefined (expects a number)

This creates a runtime type mismatch. The suggested fix to add Number.isNaN(Number(category)) is valid:

-    if (typeof category === 'string' && category in CourseNotificationCategory) {
-        return CourseNotificationCategory[category as keyof typeof CourseNotificationCategory];
-    }
+    if (
+        typeof category === 'string' &&
+        Number.isNaN(Number(category)) &&
+        category in CourseNotificationCategory
+    ) {
+        return CourseNotificationCategory[category as keyof typeof CourseNotificationCategory];
+    }

Apply the same fix to parseViewingStatus. This rejects numeric-like strings ("0", "1", etc.) while allowing valid enum member names ("GENERAL", "UNSEEN", etc.).

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
src/main/webapp/app/communication/course-notification/course-notification-websocket.service.ts
around lines 134 to 152, the enum parsing functions accept numeric-like strings
(e.g. "0") because TS numeric enums have reverse string keys, causing
CourseNotificationCategory["0"] to return a string and break the expected
numeric enum type; fix both parseCategory and parseViewingStatus by rejecting
numeric-like strings before the string-key check (e.g. ensure typeof category
=== 'string' && Number.isNaN(Number(category)) && category in Enum) so only true
enum member names are accepted and return the proper enum value.

Comment on lines +128 to +132
const channelName = typeof this.conversation === 'object' ? (this.conversation as { name?: string }).name : undefined;
this.sourceName = channelName ? `a thread in #${channelName} |` : 'a thread in #unknown |';
} else {
this.sourceName = (this.conversation as any)?.name ? `#${(this.conversation as any)?.name} |` : '#unknown |';
const channelName = typeof this.conversation === 'object' ? (this.conversation as { name?: string }).name : undefined;
this.sourceName = channelName ? `#${channelName} |` : '#unknown |';
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Simplify channel name extraction using optional chaining.

The typeof check is redundant and problematic:

  • Line 124 already guards against undefined
  • typeof null === 'object' returns true (JavaScript quirk), so null is not properly handled
  • The type assertion as { name?: string } contradicts this PR's goal of removing unsafe casts

Since this.conversation is already guarded and typed as Conversation, use optional chaining for cleaner, safer code.

Apply this diff:

         } else if (this.conversation?.type?.valueOf() === 'channel') {
             if (this.isAnswerPost) {
-                const channelName = typeof this.conversation === 'object' ? (this.conversation as { name?: string }).name : undefined;
-                this.sourceName = channelName ? `a thread in #${channelName} |` : 'a thread in #unknown |';
+                this.sourceName = `a thread in #${this.conversation.name ?? 'unknown'} |`;
             } else {
-                const channelName = typeof this.conversation === 'object' ? (this.conversation as { name?: string }).name : undefined;
-                this.sourceName = channelName ? `#${channelName} |` : '#unknown |';
+                this.sourceName = `#${this.conversation.name ?? 'unknown'} |`;
             }

Based on learnings, prefer type guards over type assertions for type safety.

🤖 Prompt for AI Agents
In
src/main/webapp/app/communication/forwarded-message/forwarded-message.component.ts
around lines 128 to 132, the current extraction uses a redundant typeof check
and an unsafe type assertion which mishandles null; replace that logic with
optional chaining to read the name safely (e.g. this.conversation?.name) and
remove the typeof and as { name?: string } cast, and if needed add a small type
guard that narrows conversation to the expected Conversation type before
accessing .name so channelName is obtained via optional chaining and defaults
remain unchanged.

Comment on lines 363 to 365
static stringifyExerciseCategories(exercise: Exercise) {
return exercise.categories?.map((category) => JSON.stringify(category) as unknown as ExerciseCategory);
return exercise.categories?.map((category) => JSON.stringify(category)) as ExerciseCategory[] | undefined;
}
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

Critical: Incorrect type assertion creates type system lie.

This function returns string[] | undefined (from JSON.stringify) but asserts the result as ExerciseCategory[] | undefined. This creates a fundamental type mismatch where the type system believes categories is an array of ExerciseCategory objects when it's actually an array of strings.

This defeats the PR's goal of improving type safety and will cause issues when other code expects ExerciseCategory[] but receives string[].

Based on learnings, prefer runtime correctness over type assertions.

Apply this diff to fix the return type:

     static stringifyExerciseCategories(exercise: Exercise) {
-        return exercise.categories?.map((category) => JSON.stringify(category)) as ExerciseCategory[] | undefined;
+        return exercise.categories?.map((category) => JSON.stringify(category)) as unknown as ExerciseCategory[] | undefined;
     }

However, the better solution is to acknowledge that the server expects stringified categories but keep proper typing. Consider updating the Exercise model to have a separate field for serialized categories, or update this function's return type to reflect reality:

-    static stringifyExerciseCategories(exercise: Exercise) {
-        return exercise.categories?.map((category) => JSON.stringify(category)) as ExerciseCategory[] | undefined;
+    static stringifyExerciseCategories(exercise: Exercise): string[] | undefined {
+        return exercise.categories?.map((category) => JSON.stringify(category));
     }

Then update call sites to handle the type correctly, or create a properly typed DTO for server communication.

📝 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
static stringifyExerciseCategories(exercise: Exercise) {
return exercise.categories?.map((category) => JSON.stringify(category) as unknown as ExerciseCategory);
return exercise.categories?.map((category) => JSON.stringify(category)) as ExerciseCategory[] | undefined;
}
static stringifyExerciseCategories(exercise: Exercise): string[] | undefined {
return exercise.categories?.map((category) => JSON.stringify(category));
}
🤖 Prompt for AI Agents
In src/main/webapp/app/exercise/services/exercise.service.ts around lines
363-365, the function currently asserts the result of JSON.stringify as
ExerciseCategory[] which is incorrect; change the function to return string[] |
undefined (remove the incorrect type assertion) and update all call sites to
handle serialized categories (or alternatively add a serializedCategories:
string[] field / DTO on the Exercise model and use that for server
communication); ensure no lingering assertions to ExerciseCategory[] remain and
adjust consumers to parse JSON.parse when they need actual ExerciseCategory
objects.

@github-project-automation github-project-automation bot moved this from Work In Progress to Ready For Review in Artemis Development Nov 28, 2025
@github-actions
Copy link

End-to-End (E2E) Test Results Summary

TestsPassed ✅SkippedFailedTime ⏱
End-to-End (E2E) Test Report1 ran1 passed0 skipped0 failed1s 615ms
TestResultTime ⏱
No test annotations available

@github-actions
Copy link

github-actions bot commented Dec 6, 2025

There hasn't been any activity on this pull request recently. Therefore, this pull request has been automatically marked as stale and will be closed if no further activity occurs within seven days. Thank you for your contributions.

@github-actions github-actions bot added stale and removed stale labels Dec 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

assessment Pull requests that affect the corresponding module client Pull requests that update TypeScript code. (Added Automatically!) communication Pull requests that affect the corresponding module core Pull requests that affect the corresponding module exam Pull requests that affect the corresponding module exercise Pull requests that affect the corresponding module fileupload Pull requests that affect the corresponding module iris Pull requests that affect the corresponding module lecture Pull requests that affect the corresponding module modeling Pull requests that affect the corresponding module programming Pull requests that affect the corresponding module quiz Pull requests that affect the corresponding module text Pull requests that affect the corresponding module

Projects

Status: Ready For Review

Development

Successfully merging this pull request may close these issues.

2 participants