Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lectures: Add hide/show functionality to PDF Preview Component #9667

Open
wants to merge 188 commits into
base: develop
Choose a base branch
from

Conversation

eceeeren
Copy link
Contributor

@eceeeren eceeeren commented Nov 4, 2024

Note: Please try to check for the PDF Preview view's functionalities again as Thumbnail Grid template is fixed from scratch :)

Checklist

General

Server

  • Important: I implemented the changes with a very good performance and prevented too many (unnecessary) and too complex database calls.
  • I strictly followed the principle of data economy for all database calls.
  • I strictly followed the server coding and design guidelines.
  • I added multiple integration tests (Spring) related to the features (with a high test coverage).
  • I added pre-authorization annotations according to the guidelines and checked the course groups for all new REST Calls (security).
  • I documented the Java code using JavaDoc style.

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 and design 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 (Jest) 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

This PR introduces a feature enabling the selective visibility of specific pages within a PDF document. Instructors often require the ability to retain certain pages in a document while temporarily withholding their visibility, for instance, until the conclusion of an exercise submission period. Currently, achieving this functionality necessitates dividing the content into multiple files, which can be cumbersome and inefficient. To address this limitation, a solution has been proposed to allow instructors to dynamically hide or reveal individual pages directly within the PDF Preview Component, enhancing the flexibility and usability of the system.

Description

A solution is implemented in PDF Preview Component, such as:

  1. Hide and Show buttons added for pages individually to able the slide hiding
  2. These buttons are shown once every page is hovered, to prevent a crowded view
  3. Once Hide Button is clicked, the page is grayed out and an "eye" icon is added to emphasise on being hidden
  4. When pages added, there are no changes as the new pages are always added at the end
  5. When some pages are deleted, even after the hidden pages are selected, previous hidden page indexes are updated accordingly
  6. When the file is saved, hidden pages indexes are saved in Slide table, therefore we can fetch them again when opening the view
  7. When the file is saved, a hidden version of the initial Attachment is created, which makes the Initial Attachment -> Parent Attachment and Hidden Attachment -> Child Attachment
  8. To avoid the JSON serialisation error, Hidden Attachments are not serialised, but Parent Attachments can be reach through parent_attachment_id
  9. When navigated to Student View, the fetching mechanism is changed to create a Download button, as it can be either the original link or the hidden link

Steps for Testing

Prerequisites:

  • 1 Instructor
  • 1 Student
  1. Log in to Artemis
  2. Navigate to Course Administration
  3. Go to 'Lecture Units'
  4. Create one Lecture Unit with a File
  5. Click "View" button
  6. Confirm that you can see "Hide" button when you over the pages individually
  7. Hide different pages and confirm that they are grayed out with "eye-slash" icon
  8. Add pages and delete some pages and ensure that initially hidden pages are not changed
  9. Save the file and confirm that you can see a success alert about "Hidden Document is created" and "Lecture Unit is updated"
  10. Return back to the previous page and confirm that the version is increased
  11. Open the View page again and confirm that the pages you deleted are deleted and hidden pages are still hidden
  12. Switch to a student user and navigate to the Course you created the Lecture Unit in
  13. Find the Lecture Unit you created and click Download
  14. The document should be opened in an external page and the deleted/hidden pages should not be shown and appended pages should be visible

Testserver States

Note

These badges show the state of the test servers.
Green = Currently available, Red = Currently locked
Click on the badges to get to the test servers.







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

Test Coverage

Class/File Line Coverage Confirmation (assert/expect)
attachment-unit.component.ts 94%
pdf-preview.component.ts 94%
pdf-preview-thumbnail-grid.component.ts 90%
lecture-unit.component.ts 100%
attachment-unit.service.spec.ts 100%

Screenshots

1. Hide Button on Hover
Screenshot 2024-11-26 at 14 48 55

2. Hidden Pages & Show Button on Hover
Screenshot 2024-11-26 at 14 49 08

Summary by CodeRabbit

  • New Features

    • Introduced a new method to retrieve the student version of an attachment unit.
    • Added functionality to manage hidden pages in attachment units.
    • Enhanced PDF preview with a new enlarged canvas component and thumbnail grid.
    • Implemented a button for downloading the original version of attachments.
  • Bug Fixes

    • Improved error handling and validation in download methods.
  • Documentation

    • Updated method documentation to reflect new parameters and functionalities.
  • Tests

    • Expanded test coverage for new features, including hidden slides and student versions.

Copy link

@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

♻️ Duplicate comments (1)
src/main/webapp/app/lecture/pdf-preview/pdf-preview-thumbnail-grid/pdf-preview-thumbnail-grid.component.ts (1)

125-136: ⚠️ Potential issue

Avoid directly mutating the input signal “hiddenPages”

You are directly toggling the same set returned by “this.hiddenPages()!”, which might cause unexpected side effects. Instead, clone it, adjust, then emit. This was flagged in a previous review comment.

🧹 Nitpick comments (4)
src/main/webapp/app/lecture/pdf-preview/pdf-preview.component.ts (1)

Line range hint 153-219: Improve error handling and state management

The error handling could be more robust, and the state management during updates could be clearer.

Consider using a state machine pattern:

enum UpdateState {
  INITIAL,
  UPDATING_ATTACHMENT,
  UPDATING_HIDDEN_PAGES,
  COMPLETE,
  ERROR
}

private updateState = signal<UpdateState>(UpdateState.INITIAL);

// Then use it in your update logic
this.updateState.set(UpdateState.UPDATING_ATTACHMENT);
// ... update logic
this.updateState.set(UpdateState.COMPLETE);
src/main/webapp/app/lecture/pdf-preview/pdf-preview-thumbnail-grid/pdf-preview-thumbnail-grid.component.ts (2)

38-38: Output property name is clear

The naming “newHiddenPagesOutput” is self-explanatory, but consider matching naming conventions such as “hiddenPagesChange” if it is effectively an update event.


61-83: Load PDF logic is well-structured but consider concurrency

The loop approach to rendering is correct. However, you may have concurrency concerns if the user triggers multiple PDF loads in quick succession. Ensure that, if a new PDF is loading, you bail out or handle the older promise properly.

Also applies to: 92-92

src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (1)

3-3: Use consistent testing approach with fakeAsync

Using fakeAsync is beneficial for controlling time-based or promise-based code. Ensure all asynchronous tests in the suite are consistent in their usage (fakeAsync vs. done callback).

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c18def3 and e1d937d.

📒 Files selected for processing (5)
  • src/main/java/de/tum/cit/aet/artemis/core/web/FileResource.java (2 hunks)
  • src/main/webapp/app/lecture/pdf-preview/pdf-preview-thumbnail-grid/pdf-preview-thumbnail-grid.component.ts (5 hunks)
  • src/main/webapp/app/lecture/pdf-preview/pdf-preview.component.ts (8 hunks)
  • src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview-thumbnail-grid.component.spec.ts (2 hunks)
  • src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/de/tum/cit/aet/artemis/core/web/FileResource.java
🧰 Additional context used
📓 Path-based instructions (4)
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview-thumbnail-grid.component.spec.ts (1)

Pattern src/test/javascript/spec/**/*.ts: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}

src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (1)

Pattern src/test/javascript/spec/**/*.ts: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}

src/main/webapp/app/lecture/pdf-preview/pdf-preview-thumbnail-grid/pdf-preview-thumbnail-grid.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/lecture/pdf-preview/pdf-preview.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

🔇 Additional comments (27)
src/main/webapp/app/lecture/pdf-preview/pdf-preview.component.ts (4)

106-113: Add cleanup for effect

The effect should be cleaned up when the component is destroyed to prevent memory leaks.


141-148: Avoid direct DOM manipulation

Using document.querySelectorAll breaks Angular's encapsulation.


185-207: Refactor nested subscriptions

The nested subscriptions make the code hard to maintain and reason about.


48-49: 🛠️ Refactor suggestion

Consider cleanup for signal subscriptions and resources

While signals are properly initialized, ensure proper cleanup in ngOnDestroy to prevent memory leaks.

Add cleanup logic to ngOnDestroy:

 ngOnDestroy() {
     this.attachmentSub?.unsubscribe();
     this.attachmentUnitSub?.unsubscribe();
+    // Cleanup signal resources
+    if (this.currentPdfUrl()) {
+        URL.revokeObjectURL(this.currentPdfUrl());
+    }
 }

Likely invalid or redundant comment.

src/main/webapp/app/lecture/pdf-preview/pdf-preview-thumbnail-grid/pdf-preview-thumbnail-grid.component.ts (9)

1-1: Organize imports properly if needed

Currently, you're importing both Angular core decorators and PDFJS from external libraries. Ensure that there is no duplication or unused imports, and that the imports are placed in a consistent order as per your style guidelines. Otherwise, this looks fine.


8-8: Great choice of icons

Using FontAwesome icons for showing/hiding pages is consistent with the design approach. No issues spotted here.


17-17: Confirm default values for signals

Check that providing default empty sets for “hiddenPages” and “isAttachmentUnit” does not create unexpected behavior if the parent component passes undefined. Setting them with strong defaults in the parent component (or handling in this component) can avoid potential runtime errors.

Also applies to: 23-24


43-44: Icon usage is consistent

Exposing FontAwesome icons as protected class members is good. This approach keeps templates and code aligned.


46-52: Use more robust logic to handle hiddenPages changes

Right now, you reset “this.newHiddenPages” with “new Set(this.hiddenPages()!)” only if hiddenPages changed. Verify that there's no scenario where the set must be reinitialized together with other inputs, e.g., if isAttachmentUnit also changes. Otherwise, this is good practice.


114-120: Canvas creation method is straightforward

Your approach ensures each page is rendered in a dedicated canvas. This method is concise.


140-151: Toggle selection logic is clear

Using a checkbox event for toggling selection is standard. The signal pattern is well applied, and the test coverage likely ensures correctness.


156-160: Enlarged canvas references are consistent

You are storing the original canvas in a signal and flipping the “isEnlargedView” boolean. This is a neat approach.


28-32: 🛠️ Refactor suggestion

Initialize signals inside ngOnInit for clarity

It’s correct that you initialize signals such as “totalPagesArray” and “loadedPages” early. However, referencing “this.hiddenPages()!” at construction time may cause issues if the parent hasn't set it yet. Consider deferring “new Set(this.hiddenPages()!)” until ngOnChanges or ngOnInit.

- newHiddenPages = signal(new Set<number>(this.hiddenPages()!));
+ newHiddenPages = signal<Set<number>>(new Set());

Then assign once in ngOnChanges or ngOnInit.

Likely invalid or redundant comment.

src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview-thumbnail-grid.component.spec.ts (8)

9-9: Ensure stable type usage for Angular tests

You rely on “ElementRef, InputSignal, Signal, SimpleChanges” from @angular/core. Confirm that your Angular version fully supports these extended features; otherwise, ensure the relevant TS config is set.


62-79: Test coverage for input changes is good

The test verifies “hiddenPages” changes. Ensure you also include an edge case for an empty or undefined set, if relevant.


81-102: Async test for currentPdfUrl changes

Well done verifying loadPdf is called with the new URL. Check for a scenario where the old PDF is still loading. Make sure the new load call does not conflict with the old one.


105-110: Test for PDF rendering is straightforward

You mock out the essential calls and confirm “createCanvas” is invoked. The assertion on “component.totalPagesArray().size” is correct.


115-116: Ensuring originalCanvas state correctness

Assigning a mock canvas to the signal and verifying the toggling is valid. This test is succinct.


123-130: Visibility toggle test

The test ensures page removal and addition to “hiddenPages”. Perfect for verifying negative and positive toggles.


132-137: Selection test with events

This test confirms pages can be selected and deselected. Good approach using the event target’s checked property.


140-160: Integration test for displayEnlargedCanvas

You properly mock up the container, return the matching canvas, and ensure “originalCanvas” is set. Nicely done.

src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (6)

75-75: Hidden slides test coverage

You now mock an observable returning [1, 2, 3]. Confirm edge cases for empty arrays or out-of-range page indexes.


128-136: Load attachment unit file with fakeAsync

Great use of tick(). This ensures that the asynchronous call is fully resolved.


Line range hint 155-174: Handle load errors with fakeAsync

Your approach for capturing and verifying error events is robust. The test is well-structured.


184-195: Subscribing/unsubscribing from attachmentUnitSub

Using unsubscribes in ngOnDestroy is best practice. Test coverage confirms correctness.


210-220: Accurate detection of hidden page IDs from DOM elements

The tests parse button IDs, extract numeric values, and confirm an empty array if none found. This is a clean approach and nicely ensures lines with invalid IDs are skipped.

Also applies to: 222-230, 232-241


426-483: Creating a student version of an attachment

• Removing pages by zero-based index is correct.
• Wrapping the result in a File object is a good approach for naming.
• The error path test is robust.

Copy link

@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 (2)
src/main/webapp/app/overview/course-lectures/attachment-unit/attachment-unit.component.ts (1)

62-70: Reduce code duplication with handleDownload

The handleOriginalVersion method shares significant code with handleDownload. Consider extracting common functionality.

Consider this refactor:

+    private downloadAttachment(link: string | undefined) {
+        if (!link || !this.lectureUnit()?.attachment?.name) {
+            console.error('Invalid attachment data');
+            return;
+        }
+        this.fileService.downloadFileByAttachmentName(link, this.lectureUnit().attachment!.name!);
+        this.onCompletion.emit({ lectureUnit: this.lectureUnit(), completed: true });
+    }
+
     handleOriginalVersion() {
         this.logEvent();
-        const link = this.lectureUnit().attachment!.link!;
-        if (link) {
-            this.fileService.downloadFileByAttachmentName(link, this.lectureUnit().attachment!.name!);
-            this.onCompletion.emit({ lectureUnit: this.lectureUnit(), completed: true });
-        }
+        this.downloadAttachment(this.lectureUnit()?.attachment?.link);
     }
src/test/javascript/spec/component/lecture-unit/attachment-unit/attachment-unit.component.spec.ts (1)

96-103: Verify arguments in handleOriginalVersion test

The test should verify the correct arguments are passed to the download method.

Add argument verification:

     it('should handle original version', () => {
         const downloadFileSpy = jest.spyOn(fileService, 'downloadFileByAttachmentName');
         const onCompletionEmitSpy = jest.spyOn(component.onCompletion, 'emit');

         component.handleOriginalVersion();

         expect(downloadFileSpy).toHaveBeenCalledOnce();
+        expect(downloadFileSpy).toHaveBeenCalledWith(
+            attachmentUnit.attachment.link,
+            attachmentUnit.attachment.name
+        );
         expect(onCompletionEmitSpy).toHaveBeenCalledOnce();
+        expect(onCompletionEmitSpy).toHaveBeenCalledWith({
+            lectureUnit: attachmentUnit,
+            completed: true
+        });
     });
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e1d937d and 50a5007.

📒 Files selected for processing (4)
  • src/main/webapp/app/overview/course-lectures/attachment-unit/attachment-unit.component.html (1 hunks)
  • src/main/webapp/app/overview/course-lectures/attachment-unit/attachment-unit.component.ts (1 hunks)
  • src/test/javascript/spec/component/lecture-unit/attachment-unit/attachment-unit.component.spec.ts (1 hunks)
  • src/test/javascript/spec/helpers/mocks/service/mock-file.service.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/webapp/app/overview/course-lectures/attachment-unit/attachment-unit.component.html
🧰 Additional context used
📓 Path-based instructions (3)
src/test/javascript/spec/helpers/mocks/service/mock-file.service.ts (1)

Pattern src/test/javascript/spec/**/*.ts: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}

src/test/javascript/spec/component/lecture-unit/attachment-unit/attachment-unit.component.spec.ts (1)

Pattern src/test/javascript/spec/**/*.ts: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}

src/main/webapp/app/overview/course-lectures/attachment-unit/attachment-unit.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 20, 2024
Copy link

@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 (2)
src/test/java/de/tum/cit/aet/artemis/core/service/FilePathServiceTest.java (1)

35-37: LGTM! Consider grouping related test cases.

The new test case correctly validates the path conversion for student-specific attachment unit files, following the established patterns and maintaining consistency with existing assertions.

Consider grouping related test cases using nested test classes (e.g., @Nested with descriptive class names) to improve organization, especially as more variants of attachment unit paths are added.

Example refactor to improve organization:

 @Test
 void testActualPathForPublicPath() {
+    @Nested
+    class AttachmentUnitPaths {
+        @Test
+        void testRegularAttachmentUnit() {
             actualPath = FilePathService.actualPathForPublicPath(URI.create("/api/files/attachments/attachment-unit/4/download.pdf"));
             assertThat(actualPath).isEqualTo(Path.of("uploads", "attachments", "attachment-unit", "4", "download.pdf"));
+        }
+        
+        @Test
+        void testStudentAttachmentUnit() {
             actualPath = FilePathService.actualPathForPublicPath(URI.create("/api/files/attachments/attachment-unit/4/student/download.pdf"));
             assertThat(actualPath).isEqualTo(Path.of("uploads", "attachments", "attachment-unit", "4", "student", "download.pdf"));
+        }
+    }
src/main/java/de/tum/cit/aet/artemis/core/web/FileResource.java (1)

581-585: Consider adding authorization check for student version access.

The endpoint should verify if the requesting user has permission to access the student version of the attachment.

         // check if hidden link is available in the attachment
         String studentVersion = attachment.getStudentVersion();
+        // Check if user has permission to access student version
+        Course course = attachmentUnit.getLecture().getCourse();
+        checkAttachmentAuthorizationOrThrow(course, attachment);
+
         if (studentVersion == null) {
             return buildFileResponse(getActualPathFromPublicPathString(attachment.getLink()), false);
         }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50a5007 and 2c14198.

📒 Files selected for processing (4)
  • src/main/java/de/tum/cit/aet/artemis/core/service/FilePathService.java (2 hunks)
  • src/main/java/de/tum/cit/aet/artemis/core/web/FileResource.java (2 hunks)
  • src/main/webapp/app/lecture/pdf-preview/pdf-preview-thumbnail-grid/pdf-preview-thumbnail-grid.component.scss (1 hunks)
  • src/test/java/de/tum/cit/aet/artemis/core/service/FilePathServiceTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/de/tum/cit/aet/artemis/core/service/FilePathService.java
🧰 Additional context used
📓 Path-based instructions (2)
src/test/java/de/tum/cit/aet/artemis/core/service/FilePathServiceTest.java (1)

Pattern src/test/java/**/*.java: test_naming: descriptive; test_size: small_specific; fixed_data: true; junit5_features: true; assert_use: assertThat; assert_specificity: true; archunit_use: enforce_package_rules; db_query_count_tests: track_performance; util_service_factory_pattern: true; avoid_db_access: true; mock_strategy: static_mocks; context_restart_minimize: true

src/main/java/de/tum/cit/aet/artemis/core/web/FileResource.java (1)

Pattern src/main/java/**/*.java: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

🔇 Additional comments (2)
src/main/java/de/tum/cit/aet/artemis/core/web/FileResource.java (1)

568-590: 🛠️ Refactor suggestion

Improve error handling and path extraction logic.

The implementation needs several improvements:

  1. Add null checks for attachmentUnit and attachment
  2. Add error handling for path operations
  3. Extract path extraction logic to a helper method
     @GetMapping("files/attachments/attachment-unit/{attachmentUnitId}/student/*")
     @EnforceAtLeastStudent
     public ResponseEntity<byte[]> getAttachmentUnitStudentVersion(@PathVariable Long attachmentUnitId) {
         log.debug("REST request to get the student version of attachment Unit : {}", attachmentUnitId);
         AttachmentUnit attachmentUnit = attachmentUnitRepository.findByIdElseThrow(attachmentUnitId);
+        if (attachmentUnit == null) {
+            throw new EntityNotFoundException("AttachmentUnit not found: " + attachmentUnitId);
+        }
+
         Attachment attachment = attachmentUnit.getAttachment();
+        if (attachment == null) {
+            throw new EntityNotFoundException("Attachment not found for unit: " + attachmentUnitId);
+        }

         // check if hidden link is available in the attachment
         String studentVersion = attachment.getStudentVersion();
         if (studentVersion == null) {
             return buildFileResponse(getActualPathFromPublicPathString(attachment.getLink()), false);
         }

-        String fileName = studentVersion.substring(studentVersion.lastIndexOf("/") + 1);
+        String fileName = extractFileName(studentVersion);

         return buildFileResponse(FilePathService.getAttachmentUnitFilePath().resolve(Path.of(attachmentUnit.getId().toString(), "student")), fileName, false);
     }

+    /**
+     * Extracts filename from a path
+     * @param path The path containing the filename
+     * @return The extracted filename
+     */
+    private String extractFileName(String path) {
+        return path.substring(path.lastIndexOf("/") + 1);
+    }

Likely invalid or redundant comment.

src/main/webapp/app/lecture/pdf-preview/pdf-preview-thumbnail-grid/pdf-preview-thumbnail-grid.component.scss (1)

53-72: Improve hover effects and transitions

The hover implementation has several issues:

  1. Use of !important should be avoided (as previously noted)
  2. Transitions could be more specific to avoid performance issues
  3. Hidden icon transitions might conflict with overlay transitions

Consider these improvements:

 .pdf-canvas-container:hover {
-    transform: scale(1.02);
+    transform: scale(1.02);
+    transition: transform 0.3s ease-out;

     button {
         opacity: 1;
+        transition: opacity 0.2s ease-in;
     }

     .pdf-overlay {
         opacity: 1;
     }

     .hidden-icon {
-        transition: transform 0.3s;
+        transition: transform 0.3s, opacity 0.2s;
         opacity: 0;
     }

     .hidden-overlay {
-        opacity: 0 !important;
+        opacity: 0;
     }
 }

+/* Add this class with higher specificity */
+.pdf-container .pdf-canvas-container[data-state="hidden"]:hover .hidden-overlay {
+    opacity: 0;
+}

Copy link

@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

♻️ Duplicate comments (3)
src/main/webapp/app/lecture/pdf-preview/pdf-preview.component.ts (2)

140-147: 🛠️ Refactor suggestion

Avoid direct DOM manipulation.

The getHiddenPages method uses direct DOM queries which can lead to issues with Angular's change detection.

Use Angular's @ViewChildren decorator instead:

@ViewChildren('hideShowButton') hideShowButtons!: QueryList<ElementRef>;

getHiddenPages(): number[] {
  return this.hideShowButtons
    .filter(btn => btn.nativeElement.classList.contains('btn-success'))
    .map(btn => {
      const match = btn.nativeElement.id.match(/hide-show-button-(\d+)/);
      return match ? parseInt(match[1], 10) : null;
    })
    .filter((id): id is number => id !== null);
}

276-291: 🛠️ Refactor suggestion

Improve type safety in updateHiddenPages.

The method has potential undefined values and unsafe type assertions.

private updateHiddenPages(pagesToDelete: number[]): void {
  const updatedHiddenPages = new Set<number>();
  const currentHiddenPages = this.hiddenPages();
  
  if (!currentHiddenPages) return;
  
  currentHiddenPages.forEach((hiddenPage) => {
    const adjustedPage = this.calculateAdjustedPage(hiddenPage, pagesToDelete);
    if (adjustedPage !== undefined) {
      updatedHiddenPages.add(adjustedPage);
    }
  });
  
  this.hiddenPages.set(updatedHiddenPages);
}

private calculateAdjustedPage(hiddenPage: number, pagesToDelete: number[]): number | undefined {
  return pagesToDelete.reduce<number | undefined>((acc, pageIndex) => {
    if (acc === undefined || acc === pageIndex + 1) {
      return undefined;
    }
    return pageIndex < acc - 1 ? acc - 1 : acc;
  }, hiddenPage);
}
src/main/java/de/tum/cit/aet/artemis/lecture/web/AttachmentUnitResource.java (1)

344-354: 🛠️ Refactor suggestion

Optimize query execution order.

The endpoint makes separate queries for slides and attachment unit, which could be optimized.

 @GetMapping("lectures/{lectureId}/attachment-units/{attachmentUnitId}/hidden-slides")
 @EnforceAtLeastEditor
 public ResponseEntity<List<Integer>> getAttachmentUnitHiddenSlides(@PathVariable Long attachmentUnitId, @PathVariable Long lectureId) {
     log.debug("REST request to get hidden slides of AttachmentUnit : {}", attachmentUnitId);
-    List<Integer> hiddenSlides = slideRepository.findHiddenSlideNumbersByAttachmentUnitId(attachmentUnitId);
     AttachmentUnit attachmentUnit = attachmentUnitRepository.findByIdElseThrow(attachmentUnitId);
     checkAttachmentUnitCourseAndLecture(attachmentUnit, lectureId);
     authorizationCheckService.checkHasAtLeastRoleInCourseElseThrow(Role.EDITOR, attachmentUnit.getLecture().getCourse(), null);
+    List<Integer> hiddenSlides = slideRepository.findHiddenSlideNumbersByAttachmentUnitId(attachmentUnitId);

     return ResponseEntity.ok().body(hiddenSlides);
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c14198 and 88d0dbd.

📒 Files selected for processing (5)
  • src/main/java/de/tum/cit/aet/artemis/lecture/service/AttachmentUnitService.java (3 hunks)
  • src/main/java/de/tum/cit/aet/artemis/lecture/web/AttachmentResource.java (1 hunks)
  • src/main/java/de/tum/cit/aet/artemis/lecture/web/AttachmentUnitResource.java (6 hunks)
  • src/main/webapp/app/lecture/attachment.service.ts (1 hunks)
  • src/main/webapp/app/lecture/pdf-preview/pdf-preview.component.ts (7 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/main/webapp/app/lecture/attachment.service.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/java/de/tum/cit/aet/artemis/lecture/web/AttachmentResource.java
  • src/main/java/de/tum/cit/aet/artemis/lecture/service/AttachmentUnitService.java
🧰 Additional context used
📓 Path-based instructions (2)
src/main/java/de/tum/cit/aet/artemis/lecture/web/AttachmentUnitResource.java (1)

Pattern src/main/java/**/*.java: naming:CamelCase; principles:{single_responsibility,small_methods,no_duplication}; db:{perf_queries,datetime_not_timestamp}; rest:{stateless,singleton,delegate_logic,http_only,minimal_dtos}; dtos:{java_records,no_entities,min_data,single_resp}; di:constructor_injection; kiss:simple_code; file_handling:os_indep_paths; practices:{least_access,avoid_transactions,code_reuse,static_member_ref,prefer_primitives}; sql:{param_annotation,uppercase,avoid_subqueries};java:avoid_star_imports

src/main/webapp/app/lecture/pdf-preview/pdf-preview.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

🔇 Additional comments (5)
src/main/webapp/app/lecture/pdf-preview/pdf-preview.component.ts (3)

47-48: LGTM! Signal declarations are well-defined.

The signals for tracking hidden pages are properly initialized as empty sets.


105-112: ⚠️ Potential issue

Add cleanup for effect subscription.

The effect needs to be cleaned up when the component is destroyed to prevent memory leaks.

+private readonly cleanupFns: Array<() => void> = [];

 constructor() {
-    effect(
+    const disposeFn = effect(
         () => {
             this.hiddenPagesChanged();
         },
         { allowSignalWrites: true },
     );
+    this.cleanupFns.push(disposeFn);
 }

+ngOnDestroy() {
+    this.cleanupFns.forEach(fn => fn());
+    // ... existing cleanup code
+}

Likely invalid or redundant comment.


299-315: ⚠️ Potential issue

Improve error handling and type safety.

The method uses unsafe null assertions and lacks proper error handling.

 async createStudentVersionOfAttachment(hiddenPages: number[]) {
     try {
-        const fileName = this.attachmentUnit()!.attachment!.name;
+        const attachmentUnit = this.attachmentUnit();
+        if (!attachmentUnit?.attachment?.name) {
+            throw new Error('Missing attachment information');
+        }
+        const fileName = attachmentUnit.attachment.name;
         const existingPdfBytes = await this.currentPdfBlob()!.arrayBuffer();
         const hiddenPdfDoc = await PDFDocument.load(existingPdfBytes);

         const pagesToDelete = hiddenPages.map((page) => page - 1).sort((a, b) => b - a);
         pagesToDelete.forEach((pageIndex) => {
             hiddenPdfDoc.removePage(pageIndex);
         });

         const pdfBytes = await hiddenPdfDoc.save();
         return new File([pdfBytes], `${fileName}.pdf`, { type: 'application/pdf' });
     } catch (error) {
         this.alertService.error('artemisApp.attachment.pdfPreview.pageDeleteError', { error: error.message });
+        return undefined;
     }
 }

Likely invalid or redundant comment.

src/main/java/de/tum/cit/aet/artemis/lecture/web/AttachmentUnitResource.java (2)

Line range hint 79-93: LGTM! Constructor injection is properly implemented.

The SlideRepository dependency is correctly injected through constructor injection following Spring best practices.


122-133: 🛠️ Refactor suggestion

Improve parameter validation and documentation.

The method parameters need better validation and documentation.

-@param hiddenPages      the pages to be hidden in the attachment unit. No hidden pages will be sent if the parameter is not set
+@param hiddenPages      comma-separated list of page numbers to be hidden (e.g., "1,2,3"). Must contain valid positive integers.
 */
 @PutMapping(value = "lectures/{lectureId}/attachment-units/{attachmentUnitId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
 @EnforceAtLeastEditor
 public ResponseEntity<AttachmentUnit> updateAttachmentUnit(@PathVariable Long lectureId, @PathVariable Long attachmentUnitId,
     @RequestPart AttachmentUnit attachmentUnit, @RequestPart Attachment attachment,
     @RequestPart(required = false) MultipartFile file,
     @RequestPart(required = false) MultipartFile studentVersion,
     @RequestParam(defaultValue = "false") boolean keepFilename,
     @RequestParam(value = "notificationText", required = false) String notificationText,
-    @RequestParam(value = "hiddenPages", required = false) String hiddenPages) {
+    @RequestParam(value = "hiddenPages", required = false) String hiddenPages) {
+    if (hiddenPages != null && !hiddenPages.matches("^\\d+(,\\d+)*$")) {
+        throw new BadRequestAlertException("Invalid format for hiddenPages", ENTITY_NAME, "invalidHiddenPages");
+    }

Likely invalid or redundant comment.

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 21, 2024
coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 21, 2024
Copy link

@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

🧹 Nitpick comments (2)
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (2)

210-242: Consider adding edge cases to strengthen test coverage.

The test suite covers basic scenarios well, but could benefit from additional edge cases:

  • Non-numeric page numbers
  • Duplicate page numbers
  • Very large page numbers

Add these test cases:

it('should handle non-numeric page numbers', () => {
    document.body.innerHTML = `
        <button id="hide-show-button-abc" class="hide-show-btn btn-success"></button>
    `;
    const hiddenPages = component.getHiddenPages();
    expect(hiddenPages).toEqual([]);
});

it('should handle duplicate page numbers', () => {
    document.body.innerHTML = `
        <button id="hide-show-button-1" class="hide-show-btn btn-success"></button>
        <button id="hide-show-button-1" class="hide-show-btn btn-success"></button>
    `;
    const hiddenPages = component.getHiddenPages();
    expect(hiddenPages).toEqual([1]);
});

475-532: Add test case for empty hidden pages array.

The test suite should verify that the student version creation works correctly when no pages are hidden.

Add this test case:

it('should return original PDF when no pages are hidden', async () => {
    const hiddenPages: number[] = [];
    const mockPdfBytes = new Uint8Array([1, 2, 3, 4]).buffer;
    const mockFileName = 'test-file';
    
    const mockAttachmentUnit = {
        attachment: {
            name: mockFileName,
        },
    };
    
    component.attachmentUnit.set(mockAttachmentUnit as any);
    component.currentPdfBlob.set(new Blob([mockPdfBytes], { type: 'application/pdf' }));
    component.currentPdfBlob()!.arrayBuffer = jest.fn().mockResolvedValue(mockPdfBytes);
    
    const result = await component.createStudentVersionOfAttachment(hiddenPages);
    
    expect(result).toBeInstanceOf(File);
    expect(result!.name).toBe(`${mockFileName}.pdf`);
    expect(result!.type).toBe('application/pdf');
    expect(PDFDocument.load).toHaveBeenCalledTimes(1);
    expect(PDFDocument.prototype.removePage).not.toHaveBeenCalled();
});
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1208baa and 0c5e79d.

📒 Files selected for processing (1)
  • src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (9 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (1)

Pattern src/test/javascript/spec/**/*.ts: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 22, 2024
Copy link

@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

🧹 Nitpick comments (1)
src/test/javascript/spec/component/lecture-unit/lecture-unit/lecture-unit.component.spec.ts (1)

84-99: Enhance test coverage for original version view.

Good practices observed:

  • Proper setup of inputs
  • Button existence verification
  • Consistent event handling
  • Comprehensive spy assertions

Consider adding these test cases:

  1. Verify button is not shown when showOriginalVersionButton is false
  2. Add specific assertions for emitted event data
 it('should handle original version view', async () => {
     const handleOriginalVersionViewSpy = jest.spyOn(component, 'handleOriginalVersionView');
     const onShowOriginalVersionEmitSpy = jest.spyOn(component.onShowOriginalVersion, 'emit');

+    // Test when button should not be shown
+    fixture.componentRef.setInput('showOriginalVersionButton', false);
+    fixture.detectChanges();
+    expect(fixture.debugElement.query(By.css('#view-original-version-button'))).toBeNull();
+
+    // Test when button should be shown
     fixture.componentRef.setInput('showOriginalVersionButton', true);
     fixture.detectChanges();

     const event = new MouseEvent('click');
     const button = fixture.debugElement.query(By.css('#view-original-version-button'));

     expect(button).not.toBeNull();

     button.nativeElement.dispatchEvent(event);

     expect(handleOriginalVersionViewSpy).toHaveBeenCalledOnce();
-    expect(onShowOriginalVersionEmitSpy).toHaveBeenCalledOnce();
+    expect(onShowOriginalVersionEmitSpy).toHaveBeenCalledExactlyOnceWith(component.lectureUnit);
 });
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c5e79d and 520865f.

⛔ Files ignored due to path filters (1)
  • src/main/resources/config/liquibase/master.xml is excluded by !**/*.xml
📒 Files selected for processing (1)
  • src/test/javascript/spec/component/lecture-unit/lecture-unit/lecture-unit.component.spec.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
src/test/javascript/spec/component/lecture-unit/lecture-unit/lecture-unit.component.spec.ts (1)

Pattern src/test/javascript/spec/**/*.ts: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}

🔇 Additional comments (1)
src/test/javascript/spec/component/lecture-unit/lecture-unit/lecture-unit.component.spec.ts (1)

Line range hint 1-99: Add test coverage for PDF Preview hide/show functionality.

Based on the PR objectives, the following test scenarios appear to be missing:

  1. Hide/show buttons visibility on page hover
  2. Page hiding/showing functionality
  3. Hidden page state persistence
  4. Visual state of hidden pages (grayed out, eye icon)

coderabbitai[bot]
coderabbitai bot previously approved these changes Dec 22, 2024
Copy link

@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

🔭 Outside diff range comments (1)
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (1)

Line range hint 274-319: Add test coverage for hidden pages in attachment unit update.

The test suite covers the basic update functionality but misses testing the interaction with hidden pages.

Add this test case:

+it('should update attachment unit successfully with hidden pages', fakeAsync(() => {
+    const hiddenPages = [1, 3];
+    jest.spyOn(component, 'getHiddenPages').mockReturnValue(hiddenPages);
+    jest.spyOn(FormData.prototype, 'append');
+
+    component.attachment.set(undefined);
+    component.attachmentUnit.set({
+        id: 1,
+        lecture: { id: 1 },
+        attachment: { id: 1, version: 1 },
+    });
+    attachmentUnitServiceMock.update.mockReturnValue(of({}));
+
+    component.updateAttachmentWithFile();
+    tick();
+
+    const formData = new FormData();
+    expect(FormData.prototype.append).toHaveBeenCalledWith('hiddenSlides', JSON.stringify(hiddenPages));
+    expect(attachmentUnitServiceMock.update).toHaveBeenCalledWith(1, 1, expect.any(FormData));
+}));
🧹 Nitpick comments (3)
src/test/javascript/spec/component/lecture-unit/attachment-unit/attachment-unit.service.spec.ts (2)

228-234: Improve subscription callback pattern.

The assignment in the subscription callback can be refactored to follow a more idiomatic pattern.

-let actualResponse: number[] | undefined;
-service
-    .getHiddenSlides(lectureId, attachmentUnitId)
-    .pipe(take(1))
-    .subscribe((response) => (actualResponse = response));
+service
+    .getHiddenSlides(lectureId, attachmentUnitId)
+    .pipe(take(1))
+    .subscribe({
+        next: (response) => {
+            expect(response).toEqual(expectedHiddenSlides);
+        }
+    });
🧰 Tools
🪛 Biome (1.9.4)

[error] 233-233: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)


224-242: Consider adding edge cases and error scenarios.

The test covers the happy path but could benefit from additional test cases:

  1. Empty array response
  2. Error response handling
  3. Invalid lecture/attachment unit IDs

Example additional test case:

it('should handle empty hidden slides array', fakeAsync(() => {
    const lectureId = 1;
    const attachmentUnitId = 42;
    const expectedHiddenSlides: number[] = [];

    service
        .getHiddenSlides(lectureId, attachmentUnitId)
        .pipe(take(1))
        .subscribe({
            next: (response) => {
                expect(response).toEqual(expectedHiddenSlides);
            }
        });

    const req = httpMock.expectOne({
        url: `api/lectures/${lectureId}/attachment-units/${attachmentUnitId}/hidden-slides`,
        method: 'GET',
    });
    req.flush(expectedHiddenSlides);
}));
🧰 Tools
🪛 Biome (1.9.4)

[error] 233-233: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (1)

128-136: Enhance async test coverage with additional assertions.

While the async test modifications correctly use fakeAsync and tick, consider adding assertions for the component's loading state.

Add these assertions to verify the loading state:

 it('should load attachment unit file and verify service calls when attachment unit data is available', fakeAsync(() => {
     routeMock.data = of({
         course: { id: 1, name: 'Example Course' },
         attachmentUnit: { id: 1, name: 'Chapter 1', lecture: { id: 1 } },
     });
+    expect(component.isLoading()).toBeTrue();
     component.ngOnInit();
     tick();
     expect(attachmentUnitServiceMock.getAttachmentFile).toHaveBeenCalledWith(1, 1);
+    expect(component.isLoading()).toBeFalse();
 }));

Also applies to: 155-174, 184-195

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 520865f and 17ee22f.

⛔ Files ignored due to path filters (1)
  • src/main/resources/config/liquibase/master.xml is excluded by !**/*.xml
📒 Files selected for processing (5)
  • src/main/java/de/tum/cit/aet/artemis/core/service/FilePathService.java (2 hunks)
  • src/test/javascript/spec/component/lecture-unit/attachment-unit/attachment-unit.service.spec.ts (1 hunks)
  • src/test/javascript/spec/component/lecture-unit/lecture-unit/lecture-unit.component.spec.ts (1 hunks)
  • src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (10 hunks)
  • src/test/javascript/spec/component/shared/http/file.service.spec.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/test/javascript/spec/component/lecture-unit/lecture-unit/lecture-unit.component.spec.ts
  • src/main/java/de/tum/cit/aet/artemis/core/service/FilePathService.java
🧰 Additional context used
📓 Path-based instructions (3)
src/test/javascript/spec/component/shared/http/file.service.spec.ts (1)

Pattern src/test/javascript/spec/**/*.ts: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}

src/test/javascript/spec/component/lecture-unit/attachment-unit/attachment-unit.service.spec.ts (1)

Pattern src/test/javascript/spec/**/*.ts: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}

src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (1)

Pattern src/test/javascript/spec/**/*.ts: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}

🪛 Biome (1.9.4)
src/test/javascript/spec/component/lecture-unit/attachment-unit/attachment-unit.service.spec.ts

[error] 233-233: The assignment should not be in an expression.

The use of assignments in expressions is confusing.
Expressions are often considered as side-effect free.

(lint/suspicious/noAssignInExpressions)

🔇 Additional comments (5)
src/test/javascript/spec/component/lecture-unit/attachment-unit/attachment-unit.service.spec.ts (1)

235-239: LGTM! HTTP mock configuration is correct.

The HTTP mock is properly configured:

  • Correct URL pattern following REST conventions
  • Appropriate HTTP method verification
  • Proper request matching
src/test/javascript/spec/component/lecture/pdf-preview/pdf-preview.component.spec.ts (3)

3-3: LGTM: Mock setup is properly configured.

The addition of fakeAsync and tick imports, along with the getHiddenSlides mock implementation, aligns well with the asynchronous nature of the component's functionality.

Also applies to: 75-75


210-242: LGTM: Comprehensive test coverage for hidden pages functionality.

The test suite thoroughly covers various scenarios for the getHiddenPages method:

  • Valid hidden page numbers
  • Empty result when no matching elements exist
  • Proper handling of invalid IDs

474-531: LGTM: Thorough test coverage for student version creation.

The test suite effectively covers:

  • Successful creation of student version with hidden pages removed
  • Error handling
  • File naming and type verification
src/test/javascript/spec/component/shared/http/file.service.spec.ts (1)

235-243: Verify implementation matches test expectations.

Let's verify that the implementation in file.service.ts correctly handles all the test cases.

✅ Verification successful

Implementation matches test expectations

The implementation in src/main/webapp/app/shared/http/file.service.ts correctly handles the test case by:

  1. Finding the last slash in the URL using lastIndexOf('/')
  2. Inserting 'student' before the filename by concatenating the path up to the last slash with '/student' and the remaining filename

The implementation is also being used correctly in the codebase, as seen in attachment-unit.component.ts where it's used as a fallback when a student version is not explicitly available.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the implementation of createStudentLink method

# Find and display the implementation
echo "Searching for createStudentLink implementation..."
ast-grep --pattern 'createStudentLink($_) {
  $$$
}'

# Check for URL handling utilities
echo "Checking for URL handling utilities..."
rg -A 5 "import.*URL" src/main/webapp/app/shared/http/file.service.ts

Length of output: 322


Script:

#!/bin/bash
# Let's try a different approach to find the implementation

# First, let's check if the file exists
echo "Checking if file.service.ts exists..."
fd "file.service.ts" src/main/webapp/app/shared/http/

# If it exists, let's look at its content
echo "Looking for createStudentLink in the implementation..."
rg -B 2 -A 5 "createStudentLink" src/main/webapp/app/shared/http/file.service.ts

# As a fallback, let's check the entire codebase for this method
echo "Searching entire codebase for createStudentLink..."
rg -B 2 -A 5 "createStudentLink" --type ts

Length of output: 6129

Comment on lines +235 to +243
describe('createStudentLink', () => {
it('should return the student version of the given link', () => {
const link = 'http://example.com/course/attachment/file.pdf';
const expectedStudentLink = 'http://example.com/course/attachment/student/file.pdf';

const result = fileService.createStudentLink(link);
expect(result).toBe(expectedStudentLink);
});
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance test coverage for createStudentLink method.

While the basic test case is good, consider adding the following test cases to improve robustness:

 describe('createStudentLink', () => {
     it('should return the student version of the given link', () => {
         const link = 'http://example.com/course/attachment/file.pdf';
         const expectedStudentLink = 'http://example.com/course/attachment/student/file.pdf';

         const result = fileService.createStudentLink(link);
         expect(result).toBe(expectedStudentLink);
     });
+
+    it('should handle URLs with query parameters', () => {
+        const link = 'http://example.com/course/attachment/file.pdf?version=1';
+        const expectedStudentLink = 'http://example.com/course/attachment/student/file.pdf?version=1';
+
+        const result = fileService.createStudentLink(link);
+        expect(result).toBe(expectedStudentLink);
+    });
+
+    it('should handle URLs with fragments', () => {
+        const link = 'http://example.com/course/attachment/file.pdf#page=1';
+        const expectedStudentLink = 'http://example.com/course/attachment/student/file.pdf#page=1';
+
+        const result = fileService.createStudentLink(link);
+        expect(result).toBe(expectedStudentLink);
+    });
+
+    it('should handle URLs that already contain student path', () => {
+        const link = 'http://example.com/course/attachment/student/file.pdf';
+
+        const result = fileService.createStudentLink(link);
+        expect(result).toBe(link);
+    });
+
+    it('should handle invalid URLs', () => {
+        const link = '';
+
+        expect(() => fileService.createStudentLink(link)).toThrow();
+    });
 });

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

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!) core Pull requests that affect the corresponding module database Pull requests that update the database. (Added Automatically!). Require a CRITICAL deployment. lecture Pull requests that affect the corresponding module server Pull requests that update Java code. (Added Automatically!) tests
Projects
Status: Ready For Review
Development

Successfully merging this pull request may close these issues.

1 participant