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

Communication Allow editors to propose FAQ #9457

Closed
wants to merge 28 commits into from

Conversation

cremertim
Copy link
Contributor

@cremertim cremertim commented Oct 12, 2024

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.
  • I added multiple integration tests (Jest) related to the features (with a high test coverage), while following the test guidelines.
  • 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

Tutors are mainly answering questions of the students, so they know what is feaquently asked. Consequently, they should be able to propose FAQs

Description

I allowed editors of the course to propose FAQ's

Steps for Testing

Prerequisites:

  • 1 Instructor
  • 1 Tutor
  • Course with FAQ enabled
  1. Log in to Artemis as Tutor

  2. Navigate to Course Management

  3. Select FAQ

  4. Propse atleast 2 FAQ's

  5. Check, that the proposed FAQ is not shown in the Course Overview

  6. Log in as Instructor

  7. Navigate to Course Management

  8. See newly proposed FAQs

  9. Accept / Reject FAQ

  10. Go to Course Overview and verify, only accepted FAQ is shown

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

Exam Mode Test

  • Test 1
  • Test 2

Performance Tests

  • Test 1
  • Test 2

Test Coverage

Screenshots

Summary by CodeRabbit

  • New Features

    • Introduced new methods for retrieving FAQs based on course ID and status.
    • Enhanced FAQ management with role-based access controls for creating, updating, and displaying FAQs.
    • Added conditional rendering for FAQ actions based on user roles.
  • Bug Fixes

    • Improved handling of FAQ states during creation and updates.
  • Documentation

    • Updated test cases to reflect changes in FAQ state management and retrieval methods.

@cremertim cremertim requested a review from a team as a code owner October 12, 2024 10:57
@github-actions github-actions bot added tests server Pull requests that update Java code. (Added Automatically!) client Pull requests that update TypeScript code. (Added Automatically!) communication Pull requests that affect the corresponding module labels Oct 12, 2024
Copy link

coderabbitai bot commented Oct 12, 2024

Walkthrough

This pull request introduces several changes across multiple files, primarily focusing on enhancing the FAQ management functionality. Key updates include the addition of methods in the FaqRepository and FaqResource for retrieving FAQs based on course ID and state, modifications to access control for creating and updating FAQs, and updates to the FAQ state enumeration. The UI components have also been adjusted to reflect these changes, including conditional rendering based on user roles.

Changes

File Change Summary
src/main/java/de/tum/cit/aet/artemis/communication/repository/FaqRepository.java Added method findAllByCourseIdAndFaqState(Long courseId, FaqState faqState) to retrieve FAQs by course ID and state.
src/main/java/de/tum/cit/aet/artemis/communication/web/FaqResource.java Added method getAllFaqForCourseByStatus(Long courseId, String faqState), updated access control for createFaq and updateFaq.
src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html Updated FAQ tab rendering logic to require at least editor access.
src/main/webapp/app/course/manage/overview/course-management-card.component.html Updated FAQ button display condition to require at least editor access.
src/main/webapp/app/entities/faq.model.ts Modified FaqState enum to assign string values to ACCEPTED, REJECTED, and PROPOSED.
src/main/webapp/app/faq/faq-update.component.ts Added isAtLeastInstructor property and updated save method to set faqState based on user role.
src/main/webapp/app/faq/faq.component.html Introduced conditional rendering for buttons and added a new "State" column in the FAQ table.
src/main/webapp/app/faq/faq.component.ts Added methods for accepting and rejecting FAQs, updated properties for managing states.
src/main/webapp/app/faq/faq.service.ts Added method findAllByCourseIdAndState for retrieving FAQs based on course ID and state.
src/main/webapp/app/overview/course-faq/course-faq.component.ts Updated service call in loadFaqs to filter FAQs by state.
src/test/java/de/tum/cit/aet/artemis/communication/FaqIntegrationTest.java Added tests for retrieving FAQs by course ID and state.
src/test/javascript/spec/component/faq/faq-update.component.spec.ts Removed import for FaqState and updated expected states in tests.
src/test/javascript/spec/component/faq/faq.component.spec.ts Added tests for accepting and rejecting FAQs.
src/test/javascript/spec/component/overview/course-faq/course-faq.component.spec.ts Updated to reflect method renaming in mock provider.
src/test/javascript/spec/service/faq.service.spec.ts Added test case for findAllByCourseIdAndState method in FaqService.

Possibly related PRs

  • Communication: Add FAQs to Artemis #9325: The changes in the main PR introduce a new method in the FaqRepository and modifications in the FaqResource class, which are directly related to the management of FAQs, similar to the changes in the Communication PR that adds a search bar for FAQs.
  • Communication: Add FAQ search bar #9423: This PR adds a search bar to the FAQ section, enhancing the functionality introduced in the main PR by allowing users to search through FAQs, which aligns with the new methods added for managing FAQs.
  • Communication: Add profile pictures to channel member overview #9450: This PR enhances the user interface by adding profile pictures to the channel member overview, which is relevant to the overall communication features being improved in the main PR, particularly in the context of user interactions with FAQs and communication channels.

Suggested labels

documentation, component:Communication, ready for review, small

Suggested reviewers

  • JohannesStoehr
  • sarpsahinalp
  • pzdr7
  • EneaGore
  • coolchock
  • MaximilianAnzinger
  • dfuchss

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

🧹 Outside diff range comments (6)
src/main/webapp/app/overview/course-faq/course-faq.component.ts (1)

Line range hint 1-98: Overall assessment: Changes improve FAQ management functionality.

The modifications in this file successfully implement the filtering of accepted FAQs, aligning with the PR objectives. The code maintains good practices in terms of naming conventions and import statements. However, ensure that these changes are consistently applied across related components and services in the application.

Consider the following to enhance the overall architecture:

  1. Implement a centralized state management solution (e.g., NgRx) for better control over FAQ states across the application.
  2. Create a separate FAQ filtering service to encapsulate the filtering logic, promoting reusability and easier maintenance.
src/main/webapp/app/faq/faq.service.ts (1)

Line range hint 1-155: Overall good adherence to coding guidelines. Minor improvements suggested.

The file generally follows the provided coding guidelines well. Here are some observations and suggestions:

  1. Consistent use of camelCase for method names and properties.
  2. Proper use of PascalCase for types.
  3. Adherence to the 'no_priv_prefix' guideline.
  4. Consistent use of single quotes for strings.
  5. Good use of TypeScript types throughout the file.

Consider the following minor improvements:

  1. Add JSDoc comments to public methods for better documentation.
  2. Consider using a more specific error type in the delete method instead of any.
  3. The toggleFilter and applyFilters methods seem to be more suited for a component or a dedicated filter service. Consider moving them if they're not directly related to FAQ data fetching or manipulation.

Example of adding JSDoc:

/**
 * Retrieves all FAQs for a given course and state.
 * @param courseId - The ID of the course
 * @param faqState - The state of the FAQs to retrieve
 * @returns An Observable of FAQs matching the criteria
 */
findAllByCourseIdAndState(courseId: number, faqState: FaqState): Observable<EntityArrayResponseType> {
    // ... (existing implementation)
}
src/test/javascript/spec/component/overview/course-faq/course-faq.component.spec.ts (1)

Line range hint 65-72: LGTM: FaqService mock updated correctly.

The mock provider for FaqService has been updated to use findAllByCourseIdAndState instead of findAllByCourseId, which aligns with the changes in the actual service.

Consider adding a comment to explain the expected parameters for findAllByCourseIdAndState, as it's not immediately clear from the mock implementation. For example:

// Mock findAllByCourseIdAndState to return all FAQs regardless of courseId and state
findAllByCourseIdAndState: (courseId: number, state: FaqState) => {
    return of(
        new HttpResponse({
            body: [faq1, faq2, faq3],
            status: 200,
        }),
    );
},
src/test/javascript/spec/component/faq/faq-update.component.spec.ts (1)

Line range hint 1-195: Consider adding test cases for the full lifecycle of proposed FAQs.

While the existing test cases have been updated to reflect the new "proposed" state for FAQs, there might be a need for additional test cases to cover the full lifecycle of a proposed FAQ.

Consider adding the following test cases:

  1. Verify that a newly created FAQ is not visible in the course overview.
  2. Test the process of an instructor accepting a proposed FAQ.
  3. Ensure that an accepted FAQ becomes visible in the course overview.
  4. Test the behavior when an instructor rejects a proposed FAQ.

These additional test cases would help ensure that the new functionality is thoroughly covered and behaves as expected throughout the FAQ lifecycle.

src/test/java/de/tum/cit/aet/artemis/communication/FaqIntegrationTest.java (1)

Line range hint 1-184: Overall, the test coverage is comprehensive and aligns with the PR objectives.

The FaqIntegrationTest class provides good coverage for FAQ management operations, including the new functionality for proposing FAQs. It tests various scenarios and user roles, adhering to the coding guidelines for integration tests.

To improve consistency, consider:

  1. Standardizing the test method naming convention. Some methods use camelCase (e.g., testgetFaqByCourseId), while others use snake_case (e.g., updateFaq_IdsDoNotMatch_shouldNotUpdateFaq). Choose one convention and apply it consistently across all test methods.

  2. Adding a test for the tutor role proposing an FAQ, as this is a key feature mentioned in the PR objectives.

Example:

@Test
@WithMockUser(username = TEST_PREFIX + "tutor1", roles = "TA")
void testTutorProposeFaq() throws Exception {
    Faq newFaq = FaqFactory.generateFaq(course1, FaqState.PROPOSED, "Tutor Question", "Tutor Answer");
    Faq returnedFaq = request.postWithResponseBody("/api/courses/" + course1.getId() + "/faqs", newFaq, Faq.class, HttpStatus.CREATED);
    assertThat(returnedFaq).isNotNull();
    assertThat(returnedFaq.getFaqState()).isEqualTo(FaqState.PROPOSED);
}

These improvements will enhance the overall quality and consistency of the test class.

src/test/javascript/spec/service/faq.service.spec.ts (1)

Line range hint 1-256: Consider enhancing test specificity and maintainability.

While the test file is well-structured, there are a couple of improvements that could be made:

  1. Use more specific expectations:
    Instead of using toEqual for comparing entire objects, consider using more specific matchers for individual properties. This can make test failures more informative.

  2. Use constants for repeated values:
    Values like courseId and color codes are repeated throughout the tests. Consider defining these as constants at the top of the describe block for better maintainability.

Here's an example of how you could implement these suggestions:

describe('Faq Service', () => {
    // ... existing setup ...

    const COURSE_ID = 1;
    const CATEGORY_COLOR = '#6ae8ac';
    const CATEGORY_NAME = 'category1';

    // ... existing tests ...

    it('should find faqs by courseId and status', () => {
        const category = {
            color: CATEGORY_COLOR,
            category: CATEGORY_NAME,
        } as FaqCategory;
        const returnedFromService = [{ ...elemDefault, categories: [JSON.stringify(category)] }];
        const expected = [{ ...elemDefault, categories: [new FaqCategory(CATEGORY_NAME, CATEGORY_COLOR)] }];

        service
            .findAllByCourseIdAndState(COURSE_ID, FaqState.ACCEPTED)
            .pipe(take(1))
            .subscribe((resp) => {
                expect(resp.body).toBeDefined();
                expect(resp.body?.length).toBe(1);
                expect(resp.body?.[0].categories?.[0]).toEqual(expect.objectContaining({
                    category: CATEGORY_NAME,
                    color: CATEGORY_COLOR
                }));
            });

        const req = httpMock.expectOne({
            url: `api/courses/${COURSE_ID}/faq-state/${FaqState.ACCEPTED}`,
            method: 'GET',
        });
        req.flush(returnedFromService);
    });

    // ... remaining tests ...
});

These changes would make the tests more robust and easier to maintain.

🧰 Tools
🪛 Biome

[error] 148-148: 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)

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE

📥 Commits

Files that changed from the base of the PR and between 36f1260 and 2be8182.

📒 Files selected for processing (17)
  • src/main/java/de/tum/cit/aet/artemis/communication/repository/FaqRepository.java (2 hunks)
  • src/main/java/de/tum/cit/aet/artemis/communication/web/FaqResource.java (5 hunks)
  • src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html (1 hunks)
  • src/main/webapp/app/course/manage/overview/course-management-card.component.html (1 hunks)
  • src/main/webapp/app/entities/faq.model.ts (1 hunks)
  • src/main/webapp/app/faq/faq-update.component.ts (5 hunks)
  • src/main/webapp/app/faq/faq.component.html (3 hunks)
  • src/main/webapp/app/faq/faq.component.ts (6 hunks)
  • src/main/webapp/app/faq/faq.service.ts (1 hunks)
  • src/main/webapp/app/overview/course-faq/course-faq.component.ts (2 hunks)
  • src/main/webapp/i18n/de/faq.json (2 hunks)
  • src/main/webapp/i18n/en/faq.json (2 hunks)
  • src/test/java/de/tum/cit/aet/artemis/communication/FaqIntegrationTest.java (4 hunks)
  • src/test/javascript/spec/component/faq/faq-update.component.spec.ts (3 hunks)
  • src/test/javascript/spec/component/faq/faq.component.spec.ts (4 hunks)
  • src/test/javascript/spec/component/overview/course-faq/course-faq.component.spec.ts (3 hunks)
  • src/test/javascript/spec/service/faq.service.spec.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (16)
src/main/java/de/tum/cit/aet/artemis/communication/repository/FaqRepository.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/java/de/tum/cit/aet/artemis/communication/web/FaqResource.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/course/manage/course-management-tab-bar/course-management-tab-bar.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/course/manage/overview/course-management-card.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/entities/faq.model.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/faq/faq-update.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/faq/faq.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/faq/faq.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/faq/faq.service.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/overview/course-faq/course-faq.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/i18n/de/faq.json (1)

Pattern src/main/webapp/i18n/de/**/*.json: German language translations should be informal (dutzen) and should never be formal (sietzen). So the user should always be addressed with "du/dein" and never with "sie/ihr".

src/test/java/de/tum/cit/aet/artemis/communication/FaqIntegrationTest.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/test/javascript/spec/component/faq/faq-update.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/faq/faq.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/overview/course-faq/course-faq.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/service/faq.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}}

🪛 Biome
src/main/webapp/app/faq/faq-update.component.ts

[error] 35-35: This type annotation is trivially inferred from its initialization.

Safe fix: Remove the type annotation.

(lint/style/noInferrableTypes)

src/main/webapp/app/faq/faq.component.ts

[error] 36-36: This type annotation is trivially inferred from its initialization.

Safe fix: Remove the type annotation.

(lint/style/noInferrableTypes)


[error] 37-37: This type annotation is trivially inferred from its initialization.

Safe fix: Remove the type annotation.

(lint/style/noInferrableTypes)

src/test/javascript/spec/service/faq.service.spec.ts

[error] 148-148: 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 (28)
src/main/java/de/tum/cit/aet/artemis/communication/repository/FaqRepository.java (2)

15-15: LGTM: Import statement added correctly.

The import for FaqState is necessary for the new method parameter and follows the coding guideline of avoiding star imports.


34-34: LGTM: New method added correctly. Consider verifying query performance.

The new method findAllByCourseIdAndFaqState follows Spring Data JPA naming conventions and is consistent with the existing code style. It adheres to the single responsibility principle by providing a specific query method.

To ensure optimal performance, please verify the generated query for this method:

If no custom query or index is found, consider adding an index to optimize this query, especially if it's expected to be called frequently.

src/main/webapp/i18n/en/faq.json (4)

7-9: LGTM: New labels for FAQ proposal workflow.

The new labels "proposeLabel", "accept", and "reject" are clear, concise, and align well with the PR objectives. They accurately represent the new functionality allowing tutors to propose FAQs and instructors to review them.


16-17: LGTM: New messages for accepting and rejecting FAQs.

The new messages for accepting and rejecting FAQs are well-structured and consistent with the existing messages. They include the FAQ ID for improved traceability and align perfectly with the new functionality described in the PR objectives.


26-27: LGTM: Addition of "state" label for FAQ table.

The new "state" label in the table object is a good addition. It's consistent with the new FAQ proposal workflow and follows the existing structure and naming conventions.


Line range hint 1-32: Overall assessment: Excellent additions for FAQ proposal workflow.

The changes to this translation file comprehensively cover the new FAQ proposal functionality described in the PR objectives. The new labels and messages are clear, consistent in style with existing content, and should effectively communicate the new workflow to users.

The additions include:

  1. Labels for proposing, accepting, and rejecting FAQs
  2. Updated messages for FAQ creation, update, and deletion (now including FAQ IDs)
  3. New messages for accepting and rejecting FAQs
  4. A new "state" label for the FAQ table

These changes align perfectly with the goal of allowing tutors to propose FAQs and instructors to review them. The translations are well-structured and should integrate seamlessly with the existing UI.

src/main/webapp/i18n/de/faq.json (2)

26-26: LGTM: Correct translation for "categories"

The translation "Kategorien" for "categories" is correct and appropriate.


27-27: LGTM: Correct translation for "state"

The translation "Status" for "state" is correct and appropriate in this context.

src/main/webapp/app/overview/course-faq/course-faq.component.ts (2)

11-11: LGTM: Import statement updated correctly.

The import statement has been properly updated to include FaqState alongside Faq. This change is consistent with its usage in the loadFaqs method and follows the coding guidelines for TypeScript files.


70-70: LGTM: Method call updated to filter accepted FAQs.

The loadFaqs method has been correctly updated to use findAllByCourseIdAndState with FaqState.ACCEPTED as a parameter. This change aligns with the PR objectives to show only accepted FAQs to students.

To ensure consistency across the application, please verify that related components or services handling FAQ data are updated accordingly. Run the following script to check for any inconsistencies:

✅ Verification successful

LGTM: Verification successful for filtering accepted FAQs.

The update to the loadFaqs method in course-faq.component.ts correctly uses findAllByCourseIdAndState with FaqState.ACCEPTED. This change is appropriately scoped to FAQ-related components, ensuring no unintended impacts on other parts of the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for potential inconsistencies in FAQ-related components and services

# Test: Search for occurrences of findAllByCourseId in other files
echo "Checking for occurrences of findAllByCourseId:"
rg --type typescript 'findAllByCourseId\(' src/main/webapp

# Test: Search for occurrences of FaqState to ensure it's used consistently
echo "Checking for occurrences of FaqState:"
rg --type typescript 'FaqState\.' src/main/webapp

Length of output: 370


Script:

#!/bin/bash
# Description: Check for potential inconsistencies in FAQ-related components and services

# Test: Search for occurrences of findAllByCourseId in TypeScript files
echo "Checking for occurrences of findAllByCourseId:"
rg --glob "*.ts" 'findAllByCourseId\(' src/main/webapp

# Test: Search for occurrences of FaqState to ensure it's used consistently in TypeScript files
echo "Checking for occurrences of FaqState:"
rg --glob "*.ts" 'FaqState\.' src/main/webapp

Length of output: 1884

src/test/javascript/spec/component/overview/course-faq/course-faq.component.spec.ts (2)

19-19: LGTM: Import statement updated correctly.

The import statement has been appropriately updated to include FaqState. This change aligns with the new usage of FaqState in the test file.


Line range hint 1-141: Overall assessment: Changes are well-implemented and maintain test coverage.

The updates to this test file accurately reflect the changes made to the CourseFaqComponent and FaqService. The introduction of FaqState and the updated method signature for fetching FAQs have been properly incorporated into the test suite. The changes maintain good test coverage and adhere to the provided coding guidelines.

Key points:

  1. Import statements have been updated correctly.
  2. The FaqService mock has been modified to use the new method signature.
  3. Test cases have been updated to reflect the new functionality and parameters.

The suggested minor improvements (adding comments for clarity and enhancing test robustness) would further increase the quality of the test suite, but the current implementation is acceptable.

src/main/webapp/app/faq/faq-update.component.ts (3)

19-19: LGTM: Import statement added correctly.

The import for AccountService is properly placed and follows the coding guidelines. It's necessary for the new functionality in the component.


49-49: LGTM: AccountService injection added correctly.

The AccountService is properly injected using the inject function, following the pattern used for other services in the component. This adheres to modern Angular dependency injection practices and the coding guidelines.


Line range hint 1-164: Overall approval with minor suggestions for improvement.

The changes to the FaqUpdateComponent successfully implement the new functionality for setting FAQ state based on user role, aligning with the PR objectives. The code adheres to the Angular style guide and coding guidelines, with only minor improvements suggested:

  1. Correct the spelling of "at least" in the new property name.
  2. Remove the unnecessary type annotation for the new property.
  3. Consider using a more descriptive variable name for clarity.

These minor adjustments will enhance code readability and consistency. Great job on implementing this feature!

src/main/webapp/app/course/manage/course-management-tab-bar/course-management-tab-bar.component.html (1)

Line range hint 75-80: LGTM! Change aligns with PR objectives.

The modification to broaden access to the FAQ tab from instructors to editors aligns well with the PR objective of allowing tutors to propose FAQs. The use of @if adheres to the coding guidelines for Angular templates.

src/test/java/de/tum/cit/aet/artemis/communication/FaqIntegrationTest.java (2)

18-18: LGTM: Import statement added correctly.

The import for FaqDTO is necessary for the new functionality and follows the coding guidelines.


35-36: LGTM: Field declaration added correctly.

The faqDTO field is appropriately declared as private and will be used in the new test methods.

src/test/javascript/spec/service/faq.service.spec.ts (1)

137-155: LGTM! The new test case is well-structured and consistent.

The new test case for findAllByCourseIdAndState follows the existing patterns in the file and correctly tests the new functionality. It properly sets up the test data, calls the service method, and verifies the response.

🧰 Tools
🪛 Biome

[error] 148-148: 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/main/webapp/app/course/manage/overview/course-management-card.component.html (1)

Line range hint 342-352: LGTM! Change aligns with PR objectives and follows coding guidelines.

The modification to allow course editors (including tutors) to access the FAQ section is in line with the PR objectives. The use of the new @if syntax instead of *ngIf adheres to the provided coding guidelines for Angular templates. The course.faqEnabled check is maintained, ensuring that the FAQ feature remains toggleable per course.

src/main/webapp/app/faq/faq.component.ts (7)

2-2: Import statements are appropriate

The added imports for Faq and FaqState are correct and necessary for managing FAQ entities and states.


18-20: Added service and model imports are correct

The imports for AccountService, Course, and TranslateService are appropriately added to support user role checks, course data access, and translations.


30-30: Assignment of FaqState for template access

Assigning FaqState to a class property allows for enum access within the template, which is appropriate.


32-32: Declaration of course property

The course property is correctly declared and is necessary for course-specific operations within the component.


53-54: Initialization of additional icons

The addition of faCancel and faCheck icons is appropriate for the new FAQ acceptance and rejection functionality.


60-61: Injection of additional services

Injecting AccountService and TranslateService is necessary for user role verification and internationalization support.


141-166: Implementation of FAQ state management methods

The rejectFaq and acceptProposedFaq methods are well-implemented, handling state updates and error cases effectively.

src/main/java/de/tum/cit/aet/artemis/communication/web/FaqResource.java (1)

Line range hint 77-87: Access control update in createFaq method

The change to @EnforceAtLeastEditor and the authorization check for Role.EDITOR in the createFaq method correctly allows editors to create FAQs, aligning with the PR objectives.

src/main/webapp/app/entities/faq.model.ts Show resolved Hide resolved
src/main/webapp/i18n/en/faq.json Outdated Show resolved Hide resolved
src/main/webapp/i18n/de/faq.json Outdated Show resolved Hide resolved
src/main/webapp/i18n/de/faq.json Outdated Show resolved Hide resolved
src/main/webapp/i18n/de/faq.json Outdated Show resolved Hide resolved
src/main/webapp/app/faq/faq.component.html Show resolved Hide resolved
coderabbitai[bot]
coderabbitai bot previously approved these changes Oct 12, 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: 10

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE

📥 Commits

Files that changed from the base of the PR and between 2be8182 and 0ab7a91.

📒 Files selected for processing (4)
  • src/main/webapp/app/faq/faq-update.component.ts (6 hunks)
  • src/main/webapp/i18n/de/faq.json (2 hunks)
  • src/main/webapp/i18n/en/faq.json (2 hunks)
  • src/test/javascript/spec/component/faq/faq-update.component.spec.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/main/webapp/app/faq/faq-update.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/i18n/de/faq.json (1)

Pattern src/main/webapp/i18n/de/**/*.json: German language translations should be informal (dutzen) and should never be formal (sietzen). So the user should always be addressed with "du/dein" and never with "sie/ihr".

src/test/javascript/spec/component/faq/faq-update.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
src/main/webapp/app/faq/faq-update.component.ts

[error] 35-35: This type annotation is trivially inferred from its initialization.

Safe fix: Remove the type annotation.

(lint/style/noInferrableTypes)

src/test/javascript/spec/component/faq/faq-update.component.spec.ts

[error] 145-145: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


[error] 172-172: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)

🔇 Additional comments (13)
src/main/webapp/i18n/en/faq.json (4)

7-9: LGTM: New labels for FAQ management.

The new labels "proposeLabel", "accept", and "reject" are clear and align well with the PR objectives of allowing editors to propose FAQs. These additions enhance the FAQ management functionality.


18-19: LGTM: New messages for accepting and rejecting FAQs.

The new messages for accepted and rejected FAQs are clear, consistent, and include the FAQ ID. These additions support the new functionality of managing proposed FAQs.


28-29: LGTM: New "state" label for FAQ table.

The addition of the "state" label in the table object is appropriate for the new FAQ management functionality. It will allow users to see the current state of each FAQ (e.g., proposed, accepted, rejected) in the UI.


Line range hint 1-33: Overall LGTM: Comprehensive enhancements to FAQ management localization.

The changes in this file effectively support the new FAQ proposal and management features outlined in the PR objectives. The new labels and messages provide clear guidance for users interacting with the enhanced FAQ functionality. The inclusion of FAQ IDs in most messages improves traceability.

These localization updates will contribute to a more intuitive user experience for both tutors proposing FAQs and instructors managing them. Great job on maintaining consistency throughout the file, with only the minor suggestion for the "deleted" message noted earlier.

src/main/webapp/i18n/de/faq.json (3)

7-9: LGTM: New labels correctly translated and use informal language.

The new labels for proposing, accepting, and rejecting FAQs are correctly translated into German and adhere to the informal language (dutzen) requirement as per the coding guidelines.


28-29: LGTM: New "state" entry correctly added.

The new entry for "state" (Status) has been correctly added to the table section. The translation is appropriate and consistent with the existing style.


Line range hint 1-34: Summary of changes and recommendations

Overall, the changes to this German localization file for FAQ-related content are well-implemented and enhance the FAQ management functionality. The new entries and modifications adhere to the informal language (dutzen) requirement as per the coding guidelines.

A few minor issues were identified:

  1. A grammatical error in the "proposedChange" message.
  2. Inconsistent use of placeholders ({{ param }} vs {{ id }}).
  3. A typo in the "accepted" message.

Please address these issues as outlined in the previous comments. Once these corrections are made, the file will be in excellent shape and ready for use.

src/test/javascript/spec/component/faq/faq-update.component.spec.ts (2)

Line range hint 1-254: Overall LGTM! Changes align well with PR objectives.

The modifications to this test file effectively support the new functionality for proposing FAQs and edits:

  1. The removal of the FaqState import and the use of string literals for FAQ states are consistent throughout the file.
  2. New test cases have been added to cover the scenarios where tutors propose new FAQs and edits to existing FAQs.
  3. The isAtLeastInstructor property has been introduced to differentiate between instructor and tutor roles in the test cases.

These changes align well with the PR objectives of allowing tutors to propose FAQs and edits for review by instructors.

To further improve the code:

  1. Consider using constants for FAQ states to improve maintainability.
  2. Refactor common setup code in similar test cases to reduce duplication and improve readability.

These suggestions will make the test suite more robust and easier to maintain in the future.

🧰 Tools
🪛 Biome

[error] 145-145: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)


14-14: LGTM! Verify FaqState usage consistency.

The removal of the FaqState import aligns with the shift to using string literals for FAQ states in the test cases. This change is consistent with the PR objectives.

To ensure consistency, let's verify that FaqState is not used elsewhere in this file:

✅ Verification successful

Verified Removal of FaqState Import

The FaqState import has been successfully removed, and no remaining usages were found in faq-update.component.spec.ts. This confirms that the change is consistent and does not impact existing functionality.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any remaining usage of FaqState in the file

# Test: Search for FaqState usage
echo "Searching for FaqState usage:"
rg "FaqState" src/test/javascript/spec/component/faq/faq-update.component.spec.ts

Length of output: 150

src/main/webapp/app/faq/faq-update.component.ts (4)

84-84: Approved: Logic for setting FAQ state is correct

The assignment of faqState based on the user's role is correctly implemented. This ensures that FAQs are appropriately marked as ACCEPTED or PROPOSED depending on the user's permissions.


35-35: Approved: Variable naming follows coding standards

The property isAtLeastInstructor is correctly named using camelCase, adhering to the project's coding guidelines.

🧰 Tools
🪛 Biome

[error] 35-35: This type annotation is trivially inferred from its initialization.

Safe fix: Remove the type annotation.

(lint/style/noInferrableTypes)


49-49: Consistent use of the inject function for dependency injection

Using the inject function to obtain instances of services like AccountService is consistent with the rest of the component and aligns with Angular's recommended practices.


114-128: Ensure success messages are correctly localized

The success messages use translation keys, which is good practice for localization. Verify that the corresponding entries exist in the localization files for both English and German, as per the project's requirements.

Run the following script to check for the existence of translation keys:

Comment on lines +13 to +17
"created": "The FAQ with ID {{ id }} was successfully created",
"updated": "The FAQ with ID {{ id }} was successfully updated",
"proposed": "The FAQ with ID {{ id }} was successfully proposed",
"proposedChange": "The changes to the FAQ with the ID {{ id }} have been successfully proposed",
"deleted": "The FAQ with ID {{ param }} was successfully deleted",
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Approve with suggestion: Consistent parameter naming.

The updated messages now include the FAQ ID, which improves traceability and user feedback. The new messages for proposed FAQs align well with the new functionality. Great improvements overall!

However, for consistency, please update the "deleted" message:

- "deleted": "The FAQ with ID {{ param }} was successfully deleted",
+ "deleted": "The FAQ with ID {{ id }} was successfully deleted",

This will maintain consistency with the other messages using "{{ id }}".

📝 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
"created": "The FAQ with ID {{ id }} was successfully created",
"updated": "The FAQ with ID {{ id }} was successfully updated",
"proposed": "The FAQ with ID {{ id }} was successfully proposed",
"proposedChange": "The changes to the FAQ with the ID {{ id }} have been successfully proposed",
"deleted": "The FAQ with ID {{ param }} was successfully deleted",
"created": "The FAQ with ID {{ id }} was successfully created",
"updated": "The FAQ with ID {{ id }} was successfully updated",
"proposed": "The FAQ with ID {{ id }} was successfully proposed",
"proposedChange": "The changes to the FAQ with the ID {{ id }} have been successfully proposed",
"deleted": "The FAQ with ID {{ id }} was successfully deleted",

Comment on lines +13 to +16
"created": "Das FAQ mit der ID {{ id }} wurde erfolgreich erstellt",
"updated": "Das FAQ mit der ID {{ id }} wurde erfolgreich aktualisiert",
"proposed": "Das FAQ mit der ID {{ id }} wurde erfolgreich vorgeschlagen",
"proposedChange": "Die Änderungen am FAQ mit der ID {{ id }} wurde erfolgreich vorgeschlagen",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix grammatical error in "proposedChange" message.

The translations are correct and use informal language as required. However, there's a grammatical error in the "proposedChange" message.

Please apply the following change:

-            "proposedChange": "Die Änderungen am FAQ mit der ID {{ id }} wurde erfolgreich vorgeschlagen",
+            "proposedChange": "Die Änderungen am FAQ mit der ID {{ id }} wurden erfolgreich vorgeschlagen",

The rest of the changes in this segment look good.

📝 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
"created": "Das FAQ mit der ID {{ id }} wurde erfolgreich erstellt",
"updated": "Das FAQ mit der ID {{ id }} wurde erfolgreich aktualisiert",
"proposed": "Das FAQ mit der ID {{ id }} wurde erfolgreich vorgeschlagen",
"proposedChange": "Die Änderungen am FAQ mit der ID {{ id }} wurde erfolgreich vorgeschlagen",
"created": "Das FAQ mit der ID {{ id }} wurde erfolgreich erstellt",
"updated": "Das FAQ mit der ID {{ id }} wurde erfolgreich aktualisiert",
"proposed": "Das FAQ mit der ID {{ id }} wurde erfolgreich vorgeschlagen",
"proposedChange": "Die Änderungen am FAQ mit der ID {{ id }} wurden erfolgreich vorgeschlagen",

Comment on lines +17 to +19
"deleted": "Das FAQ mit der ID {{ param }} wurde erfolgreich gelöscht",
"accepted": "Das FAQ mit der ID {{ id }} wurde erfolgreich akzeptieren",
"rejected": "Das FAQ mit der ID {{ id }} wurde erfolgreich abgelehnt",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix inconsistent placeholder and typo.

There are two issues in this segment:

  1. Inconsistent placeholder usage
  2. Typo in the "accepted" message

Please apply the following changes:

-            "deleted": "Das FAQ mit der ID {{ param }} wurde erfolgreich gelöscht",
+            "deleted": "Das FAQ mit der ID {{ id }} wurde erfolgreich gelöscht",
-            "accepted": "Das FAQ mit der ID {{ id }} wurde erfolgreich akzeptieren",
+            "accepted": "Das FAQ mit der ID {{ id }} wurde erfolgreich akzeptiert",

These changes will ensure consistency in placeholder usage and correct the typo in the "accepted" message.

📝 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
"deleted": "Das FAQ mit der ID {{ param }} wurde erfolgreich gelöscht",
"accepted": "Das FAQ mit der ID {{ id }} wurde erfolgreich akzeptieren",
"rejected": "Das FAQ mit der ID {{ id }} wurde erfolgreich abgelehnt",
"deleted": "Das FAQ mit der ID {{ id }} wurde erfolgreich gelöscht",
"accepted": "Das FAQ mit der ID {{ id }} wurde erfolgreich akzeptiert",
"rejected": "Das FAQ mit der ID {{ id }} wurde erfolgreich abgelehnt",

@@ -93,6 +93,7 @@ describe('FaqUpdateComponent', () => {

it('should create faq', fakeAsync(() => {
faqUpdateComponent.faq = { questionTitle: 'test1' } as Faq;
faqUpdateComponent.isAtLeastInstructor = true;
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

LGTM! Consider using a constant for FAQ states.

The changes align with the PR objectives:

  1. The introduction of isAtLeastInstructor correctly differentiates between instructor and tutor roles.
  2. The use of the string literal 'ACCEPTED' is consistent with the removal of the FaqState import.

To improve maintainability, consider defining constants for FAQ states:

const ACCEPTED_FAQ_STATE = 'ACCEPTED';
const PROPOSED_FAQ_STATE = 'PROPOSED';

// Then use them in the test cases:
expect(createSpy).toHaveBeenCalledExactlyOnceWith(courseId, { questionTitle: 'test1', faqState: ACCEPTED_FAQ_STATE });

This approach would make it easier to update state values across all test cases if needed in the future.

Also applies to: 115-117

Comment on lines +119 to 141
it('should propose faq', fakeAsync(() => {
faqUpdateComponent.faq = { questionTitle: 'test1' } as Faq;
faqUpdateComponent.isAtLeastInstructor = false;
const createSpy = jest.spyOn(faqService, 'create').mockReturnValue(
of(
new HttpResponse({
body: {
id: 3,
questionTitle: 'test1',
course: {
id: 1,
},
} as Faq,
}),
),
);

faqUpdateComponentFixture.detectChanges();
faqUpdateComponent.save();
tick();

expect(createSpy).toHaveBeenCalledExactlyOnceWith(courseId, { questionTitle: 'test1', faqState: 'PROPOSED' });
expect(faqUpdateComponent.isSaving).toBeFalse();
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

LGTM! New test case for proposing FAQs.

The new test case "should propose faq" aligns well with the PR objectives:

  1. It correctly tests the scenario where a tutor (non-instructor) proposes an FAQ.
  2. The test structure is consistent with the existing "should create faq" test case.
  3. The use of the string literal 'PROPOSED' is consistent with earlier changes.

To improve consistency and reduce duplication, consider extracting the common setup code for both FAQ creation test cases into a separate function:

function setupFaqCreationTest(isAtLeastInstructor: boolean, expectedState: string) {
  faqUpdateComponent.faq = { questionTitle: 'test1' } as Faq;
  faqUpdateComponent.isAtLeastInstructor = isAtLeastInstructor;
  const createSpy = jest.spyOn(faqService, 'create').mockReturnValue(
    of(
      new HttpResponse({
        body: {
          id: 3,
          questionTitle: 'test1',
          course: { id: 1 },
        } as Faq,
      }),
    ),
  );

  faqUpdateComponentFixture.detectChanges();
  faqUpdateComponent.save();
  tick();

  expect(createSpy).toHaveBeenCalledExactlyOnceWith(courseId, { questionTitle: 'test1', faqState: expectedState });
  expect(faqUpdateComponent.isSaving).toBeFalse();
}

// Then use it in both test cases:
it('should create faq', fakeAsync(() => setupFaqCreationTest(true, 'ACCEPTED')));
it('should propose faq', fakeAsync(() => setupFaqCreationTest(false, 'PROPOSED')));

This refactoring would reduce code duplication and make it easier to maintain these test cases in the future.

expect(faqUpdateComponent.isSaving).toBeFalse();
}));

it('should edit a faq', fakeAsync(() => {
activatedRoute.parent!.data = of({ course: { id: 1 }, faq: { id: 6 } });

faqUpdateComponent.isAtLeastInstructor = true;
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

LGTM! Consider using a constant for FAQ states.

The changes in the "should edit a faq" test case are consistent with earlier modifications and align with the PR objectives:

  1. Setting isAtLeastInstructor to true correctly represents the instructor role.
  2. The use of the string literal 'ACCEPTED' for the faqState is consistent with previous changes.

As suggested earlier, consider using a constant for the FAQ state to improve maintainability:

const ACCEPTED_FAQ_STATE = 'ACCEPTED';

// Then use it in the test case:
expect(updateSpy).toHaveBeenCalledExactlyOnceWith(courseId, { id: 6, questionTitle: 'test1Updated', faqState: ACCEPTED_FAQ_STATE });

This approach would make it easier to update state values across all test cases if needed in the future.

Also applies to: 168-169

Comment on lines +171 to +195
it('should propose to edit a faq', fakeAsync(() => {
activatedRoute.parent!.data = of({ course: { id: 1 }, faq: { id: 6 } });
faqUpdateComponent.isAtLeastInstructor = false;
faqUpdateComponentFixture.detectChanges();
faqUpdateComponent.faq = { id: 6, questionTitle: 'test1Updated' } as Faq;

expect(updateSpy).toHaveBeenCalledExactlyOnceWith(courseId, { id: 6, questionTitle: 'test1Updated' });
const updateSpy = jest.spyOn(faqService, 'update').mockReturnValue(
of<HttpResponse<Faq>>(
new HttpResponse({
body: {
id: 6,
questionTitle: 'test1Updated',
questionAnswer: 'answer',
course: {
id: 1,
},
} as Faq,
}),
),
);

faqUpdateComponent.save();
tick();
faqUpdateComponentFixture.detectChanges();
expect(updateSpy).toHaveBeenCalledExactlyOnceWith(courseId, { id: 6, questionTitle: 'test1Updated', faqState: 'PROPOSED' });
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

LGTM! New test case for proposing FAQ edits.

The new test case "should propose to edit a faq" aligns well with the PR objectives:

  1. It correctly tests the scenario where a tutor (non-instructor) proposes an edit to an existing FAQ.
  2. The test structure is consistent with the existing "should edit a faq" test case.
  3. The use of the string literal 'PROPOSED' is consistent with earlier changes.

To improve consistency and reduce duplication, consider the following refactoring:

  1. Extract common setup code for both FAQ editing test cases into a separate function:
function setupFaqEditTest(isAtLeastInstructor: boolean, expectedState: string) {
  activatedRoute.parent!.data = of({ course: { id: 1 }, faq: { id: 6 } });
  faqUpdateComponent.isAtLeastInstructor = isAtLeastInstructor;
  faqUpdateComponentFixture.detectChanges();
  faqUpdateComponent.faq = { id: 6, questionTitle: 'test1Updated' } as Faq;

  const updateSpy = jest.spyOn(faqService, 'update').mockReturnValue(
    of<HttpResponse<Faq>>(
      new HttpResponse({
        body: {
          id: 6,
          questionTitle: 'test1Updated',
          questionAnswer: 'answer',
          course: { id: 1 },
        } as Faq,
      }),
    ),
  );

  faqUpdateComponent.save();
  tick();
  faqUpdateComponentFixture.detectChanges();

  expect(updateSpy).toHaveBeenCalledExactlyOnceWith(courseId, { id: 6, questionTitle: 'test1Updated', faqState: expectedState });
}

// Then use it in both test cases:
it('should edit a faq', fakeAsync(() => setupFaqEditTest(true, 'ACCEPTED')));
it('should propose to edit a faq', fakeAsync(() => setupFaqEditTest(false, 'PROPOSED')));
  1. Use constants for FAQ states as suggested in previous comments:
const ACCEPTED_FAQ_STATE = 'ACCEPTED';
const PROPOSED_FAQ_STATE = 'PROPOSED';

// Update the function call:
it('should edit a faq', fakeAsync(() => setupFaqEditTest(true, ACCEPTED_FAQ_STATE)));
it('should propose to edit a faq', fakeAsync(() => setupFaqEditTest(false, PROPOSED_FAQ_STATE)));

These refactorings would significantly reduce code duplication, improve maintainability, and make the test cases more consistent and easier to understand.

🧰 Tools
🪛 Biome

[error] 172-172: Forbidden non-null assertion.

(lint/style/noNonNullAssertion)

Comment on lines +114 to +118
if (this.isAtLeastInstructor) {
this.alertService.success(this.translateService.instant('artemisApp.faq.created', { id: faq.id }));
} else {
this.alertService.success(this.translateService.instant('artemisApp.faq.proposed', { id: faq.id }));
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor duplicated success message code to improve maintainability

The blocks of code displaying success messages in both the creation and update scenarios are similar, with differences only in the translation keys and messages. To reduce duplication and enhance maintainability, consider refactoring the success message logic into a separate method or variable.

Apply this refactor to consolidate the success message handling:

 protected onSaveSuccess(faq: Faq) {
     this.isSaving = false;
     const isNewFaq = !this.faq.id;
     const actionKey = isNewFaq ? (this.isAtLeastInstructor ? 'created' : 'proposed') : (this.isAtLeastInstructor ? 'updated' : 'proposedChange');
     const translationKey = `artemisApp.faq.${actionKey}`;

+    this.alertService.success(this.translateService.instant(translationKey, { id: faq.id }));
     this.router.navigate(['course-management', this.courseId, 'faqs']);
 }

Then, you can remove the duplicated code blocks from both if-else branches.

Also applies to: 124-128

@@ -77,10 +81,10 @@
*/
save() {
this.isSaving = true;
this.faq.faqState = this.isAtLeastInstructor ? FaqState.ACCEPTED : FaqState.PROPOSED;
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consider handling potential null values for this.faq

While assigning faqState, ensure that this.faq is not null or undefined to prevent potential runtime errors.

Apply this minor adjustment:

 this.isSaving = true;
+if (this.faq) {
     this.faq.faqState = this.isAtLeastInstructor ? FaqState.ACCEPTED : FaqState.PROPOSED;
+}
📝 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
this.faq.faqState = this.isAtLeastInstructor ? FaqState.ACCEPTED : FaqState.PROPOSED;
this.isSaving = true;
if (this.faq) {
this.faq.faqState = this.isAtLeastInstructor ? FaqState.ACCEPTED : FaqState.PROPOSED;
}

@@ -56,6 +59,7 @@
if (course) {
this.faq.course = course;
this.loadCourseFaqCategories(course.id);
this.isAtLeastInstructor = this.accountService.isAtLeastInstructorInCourse(course);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Ensure the subscription is properly unsubscribed to prevent memory leaks

The subscribe method used on this.activatedRoute.parent?.data in ngOnInit may lead to a memory leak if the component is destroyed before the observable completes. To prevent this, consider using the takeUntil operator with an ngOnDestroy lifecycle hook to unsubscribe when the component is destroyed.

Apply this refactor to manage the subscription:

 import { Component, OnInit, OnDestroy, inject } from '@angular/core';
 import { Subject } from 'rxjs';
+import { takeUntil } from 'rxjs/operators';

 export class FaqUpdateComponent implements OnInit, OnDestroy {
     private ngUnsubscribe = new Subject<void>();

     ngOnInit() {
         // ...
-        this.activatedRoute.parent?.data.subscribe((data) => {
+        this.activatedRoute.parent?.data.pipe(takeUntil(this.ngUnsubscribe)).subscribe((data) => {
             // existing code
         });
     }

+    ngOnDestroy() {
+        this.ngUnsubscribe.next();
+        this.ngUnsubscribe.complete();
+    }
 }

Committable suggestion was skipped due to low confidence.

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

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: ASSERTIVE

📥 Commits

Files that changed from the base of the PR and between 0ab7a91 and 47e51cc.

📒 Files selected for processing (7)
  • src/main/webapp/app/faq/faq.component.html (3 hunks)
  • src/main/webapp/app/faq/faq.component.ts (7 hunks)
  • src/main/webapp/app/faq/faq.service.ts (1 hunks)
  • src/main/webapp/app/overview/course-faq/course-faq.component.ts (2 hunks)
  • src/test/javascript/spec/component/faq/faq.component.spec.ts (4 hunks)
  • src/test/javascript/spec/component/overview/course-faq/course-faq.component.spec.ts (3 hunks)
  • src/test/javascript/spec/service/faq.service.spec.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
src/main/webapp/app/faq/faq.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/faq/faq.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/faq/faq.service.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/overview/course-faq/course-faq.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/faq/faq.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/overview/course-faq/course-faq.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/service/faq.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}}

🪛 Biome
src/main/webapp/app/faq/faq.component.ts

[error] 37-37: This type annotation is trivially inferred from its initialization.

Safe fix: Remove the type annotation.

(lint/style/noInferrableTypes)


[error] 38-38: This type annotation is trivially inferred from its initialization.

Safe fix: Remove the type annotation.

(lint/style/noInferrableTypes)

src/test/javascript/spec/service/faq.service.spec.ts

[error] 148-148: 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 (16)
src/test/javascript/spec/component/overview/course-faq/course-faq.component.spec.ts (2)

19-19: LGTM: Import statement updated correctly.

The import statement has been properly updated to include FaqState. This change aligns with the modifications in the test case and suggests that the component now uses the FaqState enum.


72-72: LGTM: Mock provider updated correctly.

The mock provider for FaqService has been properly updated to use the new method name findAllByCourseIdAndState. This change reflects an update in the FaqService API and is consistent with the modifications in the test case.

src/test/javascript/spec/service/faq.service.spec.ts (1)

Line range hint 1-255: Overall, excellent test coverage and structure.

The entire test suite for FaqService is well-organized and comprehensive. It covers various scenarios and methods of the service, maintaining consistency in test structure and assertions. The new test case for findAllByCourseIdAndState integrates seamlessly with the existing tests.

Good job on:

  1. Consistent use of TestBed configuration
  2. Proper mocking of HTTP requests
  3. Thorough testing of different service methods
  4. Clear and descriptive test case names

Keep up the good work in maintaining high-quality tests!

🧰 Tools
🪛 Biome

[error] 148-148: 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/main/webapp/app/overview/course-faq/course-faq.component.ts (2)

10-10: Importing 'FaqState' is appropriate.

The addition of FaqState to the import statement is necessary for filtering FAQs by their state in the loadFaqs method.


74-74: Filtering FAQs by 'ACCEPTED' state aligns with PR objectives.

Updating the service call to findAllByCourseIdAndState(this.courseId, FaqState.ACCEPTED) ensures that only accepted FAQs are loaded, which meets the goal of displaying only accepted FAQs to students.

src/main/webapp/app/faq/faq.component.ts (5)

2-3: Imports are correctly added

The necessary imports for Faq, FaqState, and FontAwesome icons are appropriately included.


19-21: Services and models are properly imported

The imports for AccountService, Course, and TranslateService are correctly added to support the new functionalities.


31-31: New properties FaqState and course are correctly defined

The FaqState constant is assigned for template usage, and the course property is appropriately declared.

Also applies to: 33-33


55-56: FontAwesome icons are correctly initialized

The faCancel and faCheck icons are properly initialized for use in the template.


62-63: Services are properly injected

The accountService and translateService are correctly injected into the component.

src/main/webapp/app/faq/faq.component.html (2)

77-80: Addition of 'State' column looks good

The inclusion of the "State" column in the FAQ table enhances the functionality by allowing users to see the status of each FAQ and sort accordingly.


112-122: Accept/Reject functionality is correctly implemented

The conditional rendering of the accept and reject buttons for instructors when an FAQ is in the PROPOSED state is appropriate and follows the desired access control.

src/test/javascript/spec/component/faq/faq.component.spec.ts (4)

12-12: Importing 'FaqState' for FAQ state management

The addition of FaqState to the import statement is appropriate for handling FAQ states within the tests.


21-22: Importing and mocking 'AccountService' correctly

The imports for MockAccountService and AccountService are correctly added to mock authentication services in the tests.


30-30: Initializing 'faqState' to 'PROPOSED'

Setting the faqState property to FaqState.PROPOSED ensures that the FAQ is initialized with the correct state.


63-63: Providing 'MockAccountService' in TestBed

Mocking AccountService with MockAccountService in the testing module is appropriate for isolating the component during testing.

Comment on lines +49 to +55
findAllByCourseIdAndState(courseId: number, faqState: FaqState): Observable<EntityArrayResponseType> {
return this.http
.get<Faq[]>(`${this.resourceUrl}/${courseId}/faq-state/${faqState}`, {
observe: 'response',
})
.pipe(map((res: EntityArrayResponseType) => FaqService.convertFaqCategoryArrayFromServer(res)));
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

LGTM! Consider adding error handling.

The new findAllByCourseIdAndState method is well-implemented and follows Angular best practices. It maintains consistency with other methods in the service, promotes code reuse, and adheres to type safety.

Consider adding error handling to improve robustness:

import { catchError, throwError } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';

// ...

findAllByCourseIdAndState(courseId: number, faqState: FaqState): Observable<EntityArrayResponseType> {
    return this.http
        .get<Faq[]>(`${this.resourceUrl}/${courseId}/faq-state/${faqState}`, {
            observe: 'response',
        })
        .pipe(
            map((res: EntityArrayResponseType) => FaqService.convertFaqCategoryArrayFromServer(res)),
            catchError((error: HttpErrorResponse) => {
                console.error('Error fetching FAQs:', error);
                return throwError(() => new Error('Failed to fetch FAQs. Please try again later.'));
            })
        );
}

This addition will log any errors and provide a user-friendly error message.

Comment on lines +121 to +124
const findAllSpy = jest.spyOn(faqService, 'findAllByCourseIdAndState');

courseFaqComponentFixture.detectChanges();
expect(findAllSpy).toHaveBeenCalledExactlyOnceWith(1);
expect(findAllSpy).toHaveBeenCalledExactlyOnceWith(1, FaqState.ACCEPTED);
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

LGTM: Test case updated correctly, with a suggestion for improvement.

The test case has been properly updated to use findAllByCourseIdAndState and includes the FaqState.ACCEPTED parameter. The use of toHaveBeenCalledExactlyOnceWith ensures that the method is called with the correct parameters.

To further improve the test's robustness, consider implementing the suggestion from the previous review:

expect(courseFaqComponent.faqs).toBeUndefined();
courseFaqComponentFixture.detectChanges();
expect(findAllSpy).toHaveBeenCalledExactlyOnceWith(1, FaqState.ACCEPTED);
expect(courseFaqComponent.faqs).toHaveLength(3);

This addition would ensure that the component's state is updated only after the change detection.

Comment on lines +137 to +155
it('should find faqs by courseId and status', () => {
const category = {
color: '#6ae8ac',
category: 'category1',
} as FaqCategory;
const returnedFromService = [{ ...elemDefault, categories: [JSON.stringify(category)] }];
const expected = [{ ...elemDefault, categories: [new FaqCategory('category1', '#6ae8ac')] }];
const courseId = 1;
service
.findAllByCourseIdAndState(courseId, FaqState.ACCEPTED)
.pipe(take(1))
.subscribe((resp) => (expectedResult = resp));
const req = httpMock.expectOne({
url: `api/courses/${courseId}/faq-state/${FaqState.ACCEPTED}`,
method: 'GET',
});
req.flush(returnedFromService);
expect(expectedResult.body).toEqual(expected);
});
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

LGTM with a minor suggestion.

The new test case for findAllByCourseIdAndState is well-structured and covers the essential aspects of the method's functionality. It correctly sets up the test data, mocks the HTTP request, and verifies the response.

However, there's a minor improvement we can make:

On line 148, let's adjust the subscribe callback to avoid the assignment in an expression:

- .subscribe((resp) => (expectedResult = resp));
+ .subscribe((resp) => { expectedResult = resp; });

This change improves readability and addresses the static analysis warning about assignments in expressions.

The test case follows the coding guidelines for jest tests, using toEqual for object comparison and properly mocking the HTTP request. Good job on maintaining consistency with the existing test structure!

📝 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
it('should find faqs by courseId and status', () => {
const category = {
color: '#6ae8ac',
category: 'category1',
} as FaqCategory;
const returnedFromService = [{ ...elemDefault, categories: [JSON.stringify(category)] }];
const expected = [{ ...elemDefault, categories: [new FaqCategory('category1', '#6ae8ac')] }];
const courseId = 1;
service
.findAllByCourseIdAndState(courseId, FaqState.ACCEPTED)
.pipe(take(1))
.subscribe((resp) => (expectedResult = resp));
const req = httpMock.expectOne({
url: `api/courses/${courseId}/faq-state/${FaqState.ACCEPTED}`,
method: 'GET',
});
req.flush(returnedFromService);
expect(expectedResult.body).toEqual(expected);
});
it('should find faqs by courseId and status', () => {
const category = {
color: '#6ae8ac',
category: 'category1',
} as FaqCategory;
const returnedFromService = [{ ...elemDefault, categories: [JSON.stringify(category)] }];
const expected = [{ ...elemDefault, categories: [new FaqCategory('category1', '#6ae8ac')] }];
const courseId = 1;
service
.findAllByCourseIdAndState(courseId, FaqState.ACCEPTED)
.pipe(take(1))
.subscribe((resp) => { expectedResult = resp; });
const req = httpMock.expectOne({
url: `api/courses/${courseId}/faq-state/${FaqState.ACCEPTED}`,
method: 'GET',
});
req.flush(returnedFromService);
expect(expectedResult.body).toEqual(expected);
});
🧰 Tools
🪛 Biome

[error] 148-148: 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)

Comment on lines +163 to +187
rejectFaq(courseId: number, faq: Faq) {
const previousState = faq.faqState;
faq.faqState = FaqState.REJECTED;
faq.course = this.course;
this.faqService.update(courseId, faq).subscribe({
next: () => this.alertService.success(this.translateService.instant('artemisApp.faq.rejected', { id: faq.id })),
error: (error: HttpErrorResponse) => {
this.dialogErrorSource.next(error.message);
faq.faqState = previousState;
},
});
}

acceptProposedFaq(courseId: number, faq: Faq) {
const previousState = faq.faqState;
faq.faqState = FaqState.ACCEPTED;
faq.course = this.course;
this.faqService.update(courseId, faq).subscribe({
next: () => this.alertService.success(this.translateService.instant('artemisApp.faq.accepted', { id: faq.id })),
error: (error: HttpErrorResponse) => {
this.dialogErrorSource.next(error.message);
faq.faqState = previousState;
},
});
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor rejectFaq and acceptProposedFaq to reduce code duplication

The methods rejectFaq and acceptProposedFaq have similar logic. To adhere to the DRY (Don't Repeat Yourself) principle, consider refactoring them into a single reusable method.

You can create a generic method to handle FAQ state updates:

private updateFaqState(courseId: number, faq: Faq, newState: FaqState, translationKey: string) {
    const previousState = faq.faqState;
    faq.faqState = newState;
    faq.course = this.course;
    this.faqService.update(courseId, faq).subscribe({
        next: () => this.alertService.success(this.translateService.instant(translationKey, { id: faq.id })),
        error: (error: HttpErrorResponse) => {
            this.dialogErrorSource.next(error.message);
            faq.faqState = previousState;
        },
    });
}

Then, refactor the original methods to utilize this new function:

rejectFaq(courseId: number, faq: Faq) {
    this.updateFaqState(courseId, faq, FaqState.REJECTED, 'artemisApp.faq.rejected');
}

acceptProposedFaq(courseId: number, faq: Faq) {
    this.updateFaqState(courseId, faq, FaqState.ACCEPTED, 'artemisApp.faq.accepted');
}

This refactoring enhances maintainability and simplifies future updates.

Comment on lines +100 to +102
<td>
<p class="markdown-preview" [innerHTML]="faq.faqState | htmlForMarkdown"></p>
</td>
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

⚠️ Potential issue

Display user-friendly labels for FAQ states

Currently, the FAQ state is displayed using faq.faqState, which may show the raw enum value (e.g., PROPOSED, APPROVED). To improve user experience, consider displaying a user-friendly label or translating the state.

You can apply this change to use the Angular translate pipe with a translation key:

-        <p class="markdown-preview" [innerHTML]="faq.faqState | htmlForMarkdown"></p>
+        <p>{{ 'artemisApp.faq.state.' + faq.faqState | translate }}</p>

Ensure that you have the corresponding entries in your translation files, such as:

"artemisApp": {
  "faq": {
    "state": {
      "PROPOSED": "Proposed",
      "APPROVED": "Approved",
      "REJECTED": "Rejected"
    }
  }
}

Let me know if you need assistance in implementing this change or updating the translation files.

Comment on lines +129 to +142
@if (isAtleastInstrucor) {
<button
class="mt-1"
jhiDeleteButton
id="delete-faq-{{ faq.id }}"
[entityTitle]="faq.questionTitle || ''"
deleteQuestion="artemisApp.faq.delete.question"
deleteConfirmationText="artemisApp.faq.delete.typeNameToConfirm"
(delete)="deleteFaq(courseId, faq.id!)"
[dialogError]="dialogError$"
>
<fa-icon [icon]="faTrash" />
</button>
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consider allowing tutors to delete their own proposed FAQs

Currently, only instructors can delete FAQs. To enhance usability and allow tutors to manage their own content, consider permitting tutors to delete their own proposed FAQs before they are reviewed by an instructor.

faqComponentFixture.detectChanges();
faqComponent.rejectFaq(courseId, faq1);
expect(faqService.update).toHaveBeenCalledExactlyOnceWith(courseId, faq1);
expect(faq1.faqState).toEqual(FaqState.REJECTED);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use 'toBe' instead of 'toEqual' for enum comparisons

When comparing enum values, it's recommended to use the toBe matcher for strict equality instead of toEqual.

Apply the following changes:

- expect(faq1.faqState).toEqual(FaqState.REJECTED);
+ expect(faq1.faqState).toBe(FaqState.REJECTED);

...

- expect(faq1.faqState).toEqual(FaqState.PROPOSED);
+ expect(faq1.faqState).toBe(FaqState.PROPOSED);

...

- expect(faq1.faqState).toEqual(FaqState.ACCEPTED);
+ expect(faq1.faqState).toBe(FaqState.ACCEPTED);

...

- expect(faq1.faqState).toEqual(FaqState.PROPOSED);
+ expect(faq1.faqState).toBe(FaqState.PROPOSED);

Also applies to: 208-208, 216-216, 225-225

@github-actions github-actions bot added database Pull requests that update the database. (Added Automatically!). Require a CRITICAL deployment. config-change Pull requests that change the config in a way that they require a deployment via Ansible. template assessment Pull requests that affect the corresponding module buildagent Pull requests that affect the corresponding module core Pull requests that affect the corresponding module exam Pull requests that affect the corresponding module iris 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 labels Oct 13, 2024
@cremertim cremertim closed this Oct 13, 2024
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 buildagent Pull requests that affect the corresponding module client Pull requests that update TypeScript code. (Added Automatically!) communication Pull requests that affect the corresponding module config-change Pull requests that change the config in a way that they require a deployment via Ansible. core Pull requests that affect the corresponding module database Pull requests that update the database. (Added Automatically!). Require a CRITICAL deployment. exam Pull requests that affect the corresponding module iris 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 server Pull requests that update Java code. (Added Automatically!) template tests
Projects
Status: Ready For Review
Development

Successfully merging this pull request may close these issues.