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: Improve and refactor lecture attachment client validation #9893

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

Conversation

florian-glombik
Copy link
Contributor

@florian-glombik florian-glombik commented Nov 28, 2024

Checklist

General

Client

  • I strictly followed the client coding and design guidelines.
  • I added multiple integration tests (Jest) related to the features (with a high test coverage), while following the test guidelines.
  • I added multiple screenshots/screencasts of my UI changes.

Motivation and Context

This PR extends the current client validation in the lecture attachments add and edit view, converts the the validation to a signal based solution and fixes code smells.
This is a preparation for Lectures: Add status bar to lecture creation and edit mode #9655 and aims to keep #9655 at a manageable size of changes.

Description

  • Merged the column "link" and "name" into one column, as the link is indicated by its colour and being underlined anyways
  • Using angular forms based on formControlName instead of ngModel as this approach appears to be deprecated since Angular6
  • Use computed signal for isFormValid check instead of getter (= bad performance)
  • Add visual indicator next to file upload button if field is invalid
  • Minor refactorings (duplicated code moved to private helper methods & usage of descriptive method names)
  • Add client side Date Validation

Steps for Testing

Prerequisites:

  • 1 Instructor
  • 1 Lecture
  1. Create a lecture attachment
  2. Verify that the validation works as expected
  3. Save the lecture attachment
  4. Verify the details were saved properly
  5. Edit a lecture attachment
  6. Verify the details were saved properly
  7. Cancel Create/Edit and re-open an option, see that the fields will be empty again

Except for extended validation, the behavior should not differ from develop.

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

Code Review

  • Code Review 1
  • Code Review 2

Manual Tests

  • Test 1
  • Test 2

Screenshots

old version (with extra column for link)

image

new version

  • column for link removed, title is now directly linked

image

demoLectureAttachmentValidation

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced attachment management with reactive forms for improved validation and state handling.
    • Updated UI to reflect attachment creation and update states accurately.
  • Bug Fixes

    • Adjusted logic for displaying attachment names and download states.
  • Style

    • Introduced a new CSS class for improved styling of invalid file inputs.
  • Tests

    • Updated test suite to align with new state management and form handling logic.

@florian-glombik florian-glombik added this to the 7.7.3 milestone Nov 28, 2024
@florian-glombik florian-glombik self-assigned this Nov 28, 2024
@github-actions github-actions bot added tests client Pull requests that update TypeScript code. (Added Automatically!) labels Nov 28, 2024
@florian-glombik florian-glombik changed the title Lectures: Improve and refactor lecture attachment validation Lectures: Improve and refactor lecture attachment client validation Nov 28, 2024
@florian-glombik florian-glombik marked this pull request as ready for review November 28, 2024 04:51
@florian-glombik florian-glombik requested a review from a team as a code owner November 28, 2024 04:51
Copy link

coderabbitai bot commented Nov 28, 2024

Walkthrough

The changes in this pull request focus on enhancing the LectureAttachmentsComponent within an Angular application. Key modifications include the centralization of attachment creation and update logic, the introduction of reactive forms for better state management, and updates to the UI for attachment handling. The component's styling has been adjusted with a new CSS class, and the test suite has been restructured to align with the updated component logic. Overall, these changes aim to streamline the attachment management process and improve form handling.

Changes

File Change Summary
src/main/webapp/app/lecture/lecture-attachments.component.html Centralized attachment logic using attachmentToBeUpdatedOrCreated(), updated table row class binding, modified download link logic, and revised form handling.
src/main/webapp/app/lecture/lecture-attachments.component.scss Added new CSS class .invalid-file-input-margin with left padding of 0.5em.
src/main/webapp/app/lecture/lecture-attachments.component.ts Introduced reactive forms, new interface LectureAttachmentFormData, updated methods for attachment handling, and replaced attachmentToBeCreated with signals.
src/test/javascript/spec/component/lecture/lecture-attachments.component.spec.ts Updated test suite to reflect changes in attachment handling, replaced MockComponent with MockModule, and adjusted assertions for new state management methods.

Possibly related PRs

Suggested labels

bugfix, code quality, small, ready to merge

Suggested reviewers

  • JohannesStoehr
  • HawKhiem
  • BBesrour
  • sachmii
  • az108
  • Feras797

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @coderabbitai title anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

🧹 Outside diff range and nitpick comments (8)
src/main/webapp/app/lecture/lecture-attachments.component.html (2)

55-55: Consider using class binding instead of ngClass

For better performance, consider replacing ngClass with class binding when there's only one class to toggle:

-<tr [ngClass]="{ 'edit-overlay-container': attachmentToBeUpdatedOrCreated()?.id === attachment?.id }">
+<tr [class.edit-overlay-container]="attachmentToBeUpdatedOrCreated()?.id === attachment?.id">

142-143: Consider adding validation error messages

The form fields use formControlName for validation, but there are no visible error messages to guide users when validation fails. Consider adding error messages for better user experience.

 <input type="text" class="form-control" id="attachmentName" name="attachmentName" formControlName="attachmentName" />
+<div class="invalid-feedback" *ngIf="form.get('attachmentName')?.invalid && form.get('attachmentName')?.touched">
+    <div *ngIf="form.get('attachmentName')?.errors?.['required']">Name is required</div>
+</div>
src/test/javascript/spec/component/lecture/lecture-attachments.component.spec.ts (3)

151-162: Enhance file upload test assertions

While the test correctly verifies the basic file upload flow, consider adding assertions to verify the form's validity state after setting values.

Add these assertions:

 comp.form.patchValue({
     attachmentName: 'Test File Name',
     releaseDate: dayjs(),
 });
 fixture.detectChanges();
+expect(comp.form.valid).toBeTrue();
+expect(comp.isFormValid()).toBeTrue();
 expect(uploadAttachmentButton.nativeElement.disabled).toBeFalse();

221-227: Add error state validation assertions

The error handling test should verify that the form remains in an invalid state after the error occurs.

Add these assertions:

 expect(comp.attachmentToBeUpdatedOrCreated()).toEqual(attachment);
 expect(comp.attachmentFile()).toBeUndefined();
 expect(comp.erroredFile).toEqual(file);
 expect(comp.errorMessage).toBe(errorMessage);
 expect(comp.fileInput.nativeElement.value).toBe('');
+expect(comp.isFormValid()).toBeFalse();

Line range hint 1-419: Consider adding test documentation

While the test cases are well-structured, adding descriptive comments for each test group would improve maintainability. Consider using Jest's describe blocks to group related tests and document the testing strategy for:

  • Form validation scenarios
  • File upload workflows
  • Error handling cases

Example structure:

describe('Form Validation', () => {
    // Group form validation related tests
});

describe('File Upload Workflow', () => {
    // Group file upload related tests
});

describe('Error Handling', () => {
    // Group error handling related tests
});
src/main/webapp/app/lecture/lecture-attachments.component.ts (3)

45-48: Prefer constructor injection over inject() for dependencies

In components, it is recommended to use constructor injection for dependencies instead of the inject() function. This enhances readability and consistency across the codebase. Consider injecting ActivatedRoute, AttachmentService, LectureService, FileService, and FormBuilder via the constructor.

Apply this diff to implement constructor injection:

- private readonly activatedRoute = inject(ActivatedRoute);
- private readonly attachmentService = inject(AttachmentService);
- private readonly lectureService = inject(LectureService);
- private readonly fileService = inject(FileService);
- private readonly formBuilder = inject(FormBuilder);

+ constructor(
+     private readonly activatedRoute: ActivatedRoute,
+     private readonly attachmentService: AttachmentService,
+     private readonly lectureService: LectureService,
+     private readonly fileService: FileService,
+     private readonly formBuilder: FormBuilder
+ ) {
+     effect(
+         () => {
+             this.notificationText = undefined;
+             this.routeDataSubscription?.unsubscribe();
+             this.routeDataSubscription = this.activatedRoute.parent!.data.subscribe(({ lecture }) => {
+                 if (this.lectureId()) {
+                     this.lectureService.findWithDetails(this.lectureId()!).subscribe((lectureResponse: HttpResponse<Lecture>) => {
+                         this.lecture.set(lectureResponse.body!);
+                         this.loadAttachments();
+                     });
+                 } else {
+                     this.lecture.set(lecture);
+                     this.loadAttachments();
+                 }
+             });
+         },
+         { allowSignalWrites: true },
+     );
+ }

178-185: Simplify form resetting by calling this.form.reset() without parameters

In the clearFormValues() method, you are resetting the form and explicitly setting each control to undefined. Since you intend to reset all form controls to their initial state, you can simplify this by calling this.form.reset() without parameters.

Apply this diff to simplify the form resetting:

private clearFormValues(): void {
-    this.form.reset({
-        attachmentName: undefined,
-        attachmentFileName: undefined,
-        releaseDate: undefined,
-        notificationText: undefined,
-    });
+    this.form.reset();
}

207-209: Reset file input value safely

In the editAttachment() method, you check if fileInput exists before resetting its value. Ensure consistent null checks across your methods to prevent potential errors. Review other methods where fileInput is accessed without null checks.

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4eaba4e and ed4f33e.

📒 Files selected for processing (4)
  • src/main/webapp/app/lecture/lecture-attachments.component.html (9 hunks)
  • src/main/webapp/app/lecture/lecture-attachments.component.scss (1 hunks)
  • src/main/webapp/app/lecture/lecture-attachments.component.ts (8 hunks)
  • src/test/javascript/spec/component/lecture/lecture-attachments.component.spec.ts (10 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/main/webapp/app/lecture/lecture-attachments.component.scss
🧰 Additional context used
📓 Path-based instructions (3)
src/main/webapp/app/lecture/lecture-attachments.component.html (1)

Pattern src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/app/lecture/lecture-attachments.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/test/javascript/spec/component/lecture/lecture-attachments.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 (6)
src/main/webapp/app/lecture/lecture-attachments.component.html (3)

64-70: LGTM! Well-implemented download status indication

The download status handling with spinner animation provides good user feedback, and the implementation follows Angular best practices.


Line range hint 93-116: LGTM! Well-structured action buttons with proper state management

The implementation includes:

  • Proper disabled state management
  • Confirmation dialog for delete action
  • Accessibility support

166-168: LGTM! Well-implemented file input validation

The file input validation includes:

  • Visual feedback for invalid files
  • Proper spacing with CSS
  • Clear state management
src/test/javascript/spec/component/lecture/lecture-attachments.component.spec.ts (1)

12-12: LGTM: Test setup properly configured for reactive forms

The test module setup correctly incorporates ReactiveFormsModule and properly mocks the OwlDateTime modules using MockModule, which aligns with the migration to reactive forms.

Also applies to: 18-18, 24-24, 89-97

src/main/webapp/app/lecture/lecture-attachments.component.ts (2)

138-139: Verify manual incrementation of the version property

You are manually incrementing the version property of attachmentToBeUpdatedOrCreated. Ensure that this aligns with your application's versioning strategy and that version conflicts are properly managed. If the server also handles versioning, manually incrementing it on the client side might lead to inconsistencies.


66-69: ⚠️ Potential issue

Correct Subject cleanup in ngOnDestroy()

In the ngOnDestroy() method, you are calling unsubscribe() on dialogErrorSource, which is a Subject. Subjects do not have an unsubscribe() method. Instead, you should call complete() to properly terminate the Subject.

Apply this diff to fix the cleanup logic:

- this.dialogErrorSource.unsubscribe();
+ this.dialogErrorSource.complete();

Likely invalid or redundant comment.

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 and nitpick comments (4)
src/main/webapp/app/lecture/lecture-attachments.component.ts (4)

69-74: Consider adding more specific validators

The form controls could benefit from more specific validators:

  • attachmentName could have a max length validator
  • releaseDate should have a validator to ensure it's not in the past
 form: FormGroup = this.formBuilder.group({
-    attachmentName: [undefined as string | undefined, [Validators.required]],
+    attachmentName: [undefined as string | undefined, [Validators.required, Validators.maxLength(255)]],
     attachmentFileName: [undefined as string | undefined],
-    releaseDate: [undefined as dayjs.Dayjs | undefined],
+    releaseDate: [undefined as dayjs.Dayjs | undefined, [this.futureDateValidator]],
     notificationText: [undefined as string | undefined],
 });

76-81: Simplify form validation logic

The validation logic using computed signals is complex and tightly coupled with the date picker component. Consider extracting the validation logic into a separate method for better maintainability.

-isFormValid = computed(() => this.statusChanges() === 'VALID' && this.isFileSelectionValid() && this.datePickerComponent()?.isValid());
+private validateDatePicker = computed(() => this.datePickerComponent()?.isValid() ?? false);
+isFormValid = computed(() => {
+    return this.statusChanges() === 'VALID' 
+        && this.isFileSelectionValid() 
+        && this.validateDatePicker();
+});

135-142: Simplify attachment save logic

The save logic contains multiple null checks and property assignments. Consider extracting the attachment preparation logic into a separate method.

+private prepareAttachmentForSave(): void {
+    if (!this.attachmentToBeUpdatedOrCreated()) return;
+    
+    const attachment = this.attachmentToBeUpdatedOrCreated()!;
+    attachment.version!++;
+    attachment.uploadDate = dayjs();
+    attachment.name = this.form.value.attachmentName ?? undefined;
+    attachment.releaseDate = this.form.value.releaseDate ?? undefined;
+}

 saveAttachment(): void {
-    if (!this.attachmentToBeUpdatedOrCreated()) {
-        return;
-    }
-    this.attachmentToBeUpdatedOrCreated()!.version!++;
-    this.attachmentToBeUpdatedOrCreated()!.uploadDate = dayjs();
-    this.attachmentToBeUpdatedOrCreated()!.name = this.form.value.attachmentName ?? undefined;
-    this.attachmentToBeUpdatedOrCreated()!.releaseDate = this.form.value.releaseDate ?? undefined;
+    this.prepareAttachmentForSave();
     this.notificationText = this.form.value.notificationText ?? undefined;

286-289: Improve filename regex pattern

The current regex pattern for extracting file extension could be more robust to handle edge cases like multiple dots in filename.

-private determineAttachmentNameBasedOnFileName(fileName: string): string {
-    const FILE_EXTENSION_REGEX = /\.[^/.]+$/;
-    return fileName.replace(FILE_EXTENSION_REGEX, '');
+private determineAttachmentNameBasedOnFileName(fileName: string): string {
+    const lastDotIndex = fileName.lastIndexOf('.');
+    return lastDotIndex === -1 ? fileName : fileName.substring(0, lastDotIndex);
}
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ed4f33e and 738dd2b.

📒 Files selected for processing (2)
  • src/main/webapp/app/lecture/lecture-attachments.component.html (9 hunks)
  • src/main/webapp/app/lecture/lecture-attachments.component.ts (8 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/webapp/app/lecture/lecture-attachments.component.html
🧰 Additional context used
📓 Path-based instructions (1)
src/main/webapp/app/lecture/lecture-attachments.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 (2)
src/main/webapp/app/lecture/lecture-attachments.component.ts (2)

Line range hint 1-22: LGTM! Clean imports and well-structured interface

The new imports and interface definition align well with the transition to reactive forms. The LectureAttachmentFormData interface is well-structured with appropriate optional properties.


196-198: ⚠️ Potential issue

Add null check for fileInput access

This is a duplicate of a previous review comment about adding a null check for fileInput.

-this.fileInput.nativeElement.value = '';
+if (this.fileInput?.nativeElement) {
+    this.fileInput.nativeElement.value = '';
+}

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!) refactoring tests
Projects
Status: Ready For Review
Development

Successfully merging this pull request may close these issues.

1 participant