-
Notifications
You must be signed in to change notification settings - Fork 291
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
Programming exercises
: Fix performance bug of ide preferences in scores page
#9596
base: develop
Are you sure you want to change the base?
Conversation
WalkthroughThe pull request introduces several modifications across multiple files, primarily focusing on enhancing the interactivity and responsiveness of the Changes
Possibly related PRs
Suggested labels
Suggested reviewers
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? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 13
🧹 Outside diff range and nitpick comments (10)
src/main/webapp/app/shared/user-settings/ide-preferences/ide-settings.component.html (2)
4-6
: Consider using semantic HTML elements.While the direct display of help text improves UX as intended, consider wrapping these spans in a more semantic HTML element (e.g.,
<div role="definition">
) to maintain accessibility and semantic structure.- <span jhiTranslate="artemisApp.userSettings.idePreferencesPage.description"></span> - <br /> - <span jhiTranslate="artemisApp.userSettings.idePreferencesPage.jetbrainsHelp"></span> + <div role="definition"> + <span jhiTranslate="artemisApp.userSettings.idePreferencesPage.description"></span> + <br /> + <span jhiTranslate="artemisApp.userSettings.idePreferencesPage.jetbrainsHelp"></span> + </div>
9-9
: *Consider migrating ngTemplateOutlet to newer syntax.While not strictly required by the guidelines, for consistency, consider migrating the
*ngTemplateOutlet
to the newer control flow syntax when it becomes available in future Angular versions.Also applies to: 22-22
src/test/javascript/spec/service/settings/ide-settings.service.spec.ts (1)
Line range hint
51-83
: Add error cases and enhance expectations for CRUD operations.The existing tests are good but could be more robust with error handling and specific expectations.
Consider adding these improvements:
it('should save IDE preference', () => { const programmingLanguage = ProgrammingLanguage.JAVA; const ide: Ide = { name: 'VS Code', deepLink: 'vscode://vscode.git/clone?url={cloneUrl}' }; const mockResponse: IdeMappingDTO = { programmingLanguage, ide }; + let completed = false; service.saveIdePreference(programmingLanguage, ide).subscribe({ - expect(savedIde).toEqual(ide); + next: (savedIde) => { + expect(savedIde).toContainEntries(Object.entries(ide)); + expect(Object.keys(savedIde)).toHaveLength(2); + }, + complete: () => { completed = true; } }); const req = httpMock.expectOne((request) => request.url === service.ideSettingsUrl && request.params.get('programmingLanguage') === programmingLanguage); expect(req.request.method).toBe('PUT'); req.flush(mockResponse); + expect(completed).toBeTrue(); }); +it('should handle error when saving IDE preference', () => { + const programmingLanguage = ProgrammingLanguage.JAVA; + const ide: Ide = { name: 'VS Code', deepLink: 'vscode://vscode.git/clone?url={cloneUrl}' }; + + service.saveIdePreference(programmingLanguage, ide).subscribe({ + error: (error) => { + expect(error.status).toBe(400); + } + }); + + const req = httpMock.expectOne((request) => request.url === service.ideSettingsUrl); + req.flush('Invalid data', { status: 400, statusText: 'Bad Request' }); +});src/main/webapp/app/shared/user-settings/ide-preferences/ide-settings.component.ts (2)
18-20
: Consider adding explicit type for the WritableSignal.For better type safety and readability, consider explicitly declaring the generic type for the WritableSignal.
-programmingLanguageToIde: WritableSignal<Map<ProgrammingLanguage, Ide>>; +programmingLanguageToIde: WritableSignal<Map<ProgrammingLanguage, Ide>> = signal(new Map());
Line range hint
34-50
: Consider adding error handling for subscriptions.While the subscription management is good, consider adding error handling for robustness.
-const predefinedIdesSubscription = this.ideSettingsService.loadPredefinedIdes().subscribe((ides) => { +const predefinedIdesSubscription = this.ideSettingsService.loadPredefinedIdes().subscribe({ + next: (ides) => { this.PREDEFINED_IDE = ides; + }, + error: (error) => { + console.error('Failed to load predefined IDEs:', error); + // Consider showing a user-friendly error message + } });src/test/javascript/spec/component/settings/ide/ide-settings.component.spec.ts (1)
Line range hint
1-8
: Replace CUSTOM_ELEMENTS_SCHEMA with explicit component declarations.The use of CUSTOM_ELEMENTS_SCHEMA is discouraged as it can hide potential template errors. Instead, use NgMocks to mock child components.
Apply these changes:
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { MockComponent } from 'ng-mocks'; import { of } from 'rxjs'; -import { IdeSettingsComponent } from 'app/shared/user-settings/ide-preferences/ide-settings.component'; +import { IdeSettingsComponent } from '@shared/user-settings/ide-preferences/ide-settings.component';And update the TestBed configuration:
declarations: [ IdeSettingsComponent, + // Add mocked child components here using MockComponent ], -schemas: [CUSTOM_ELEMENTS_SCHEMA],src/main/webapp/app/shared/components/code-button/code-button.component.html (1)
Line range hint
1-108
: Consider refactoring template for better maintainability.The template could benefit from the following improvements:
- Move inline styles to CSS files (e.g.,
style="margin: 0; max-width: 100%"
,style="min-width: 100px"
).- Consider breaking down the large template into smaller, reusable components:
- Repository URL section
- IDE buttons section
- Clone options section
Example refactor for the repository URL section:
// repository-url.component.html <div class="d-flex repository-url-container"> <ng-container *ngTemplateOutlet="sshDropdown"></ng-container> <pre class="clone-url" [ngClass]="urlBoxClasses" [cdkCopyToClipboard]="repositoryUri" (cdkCopyToClipboardCopied)="onCopy($event)" >{{ repositoryUri }}</pre> <ng-container *ngTemplateOutlet="repositoryLinks"></ng-container> </div>// repository-url.component.scss .repository-url-container { margin: 0; max-width: 100%; } .clone-url { &.url-box-remove-line-left { // styles } &.url-box-remove-line-right { // styles } }src/main/webapp/app/shared/components/code-button/code-button.component.ts (2)
65-67
: Consider adding type safety to the Map declarationWhile the Map implementation is correct, consider explicitly declaring the value type for better type safety.
- programmingLanguageToIde: Map<ProgrammingLanguage, Ide>; + programmingLanguageToIde: Map<ProgrammingLanguage, Ide> = new Map<ProgrammingLanguage, Ide>();
342-342
: Consider adding null check for fallbackIdeWhile using the static fallbackIde is correct, consider adding a null check to handle cases where the fallbackIde might not be initialized.
- IdeSettingsService.fallbackIde + IdeSettingsService.fallbackIde ?? { id: 'default', name: 'Default IDE' }src/main/webapp/app/shared/user-settings/ide-preferences/ide-settings.service.ts (1)
16-16
: Remove unused 'error' propertyThe
error
property is declared but never used within the service. Removing it can clean up the code.Apply this diff to remove the unused property:
- error?: string;
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
📒 Files selected for processing (8)
- src/main/webapp/app/shared/components/code-button/code-button.component.html (1 hunks)
- src/main/webapp/app/shared/components/code-button/code-button.component.ts (6 hunks)
- src/main/webapp/app/shared/user-settings/ide-preferences/ide-settings.component.html (1 hunks)
- src/main/webapp/app/shared/user-settings/ide-preferences/ide-settings.component.ts (3 hunks)
- src/main/webapp/app/shared/user-settings/ide-preferences/ide-settings.scss (0 hunks)
- src/main/webapp/app/shared/user-settings/ide-preferences/ide-settings.service.ts (3 hunks)
- src/test/javascript/spec/component/settings/ide/ide-settings.component.spec.ts (2 hunks)
- src/test/javascript/spec/service/settings/ide-settings.service.spec.ts (2 hunks)
💤 Files with no reviewable changes (1)
- src/main/webapp/app/shared/user-settings/ide-preferences/ide-settings.scss
🧰 Additional context used
📓 Path-based instructions (7)
src/main/webapp/app/shared/components/code-button/code-button.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/shared/components/code-button/code-button.component.ts (1)
src/main/webapp/app/shared/user-settings/ide-preferences/ide-settings.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/shared/user-settings/ide-preferences/ide-settings.component.ts (1)
src/main/webapp/app/shared/user-settings/ide-preferences/ide-settings.service.ts (1)
src/test/javascript/spec/component/settings/ide/ide-settings.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/settings/ide-settings.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}}
🔇 Additional comments (11)
src/main/webapp/app/shared/user-settings/ide-preferences/ide-settings.component.html (2)
Line range hint
12-12
: LGTM! Proper usage of new Angular control flow syntax.The template correctly implements the new
@for
and@if
syntax as per coding guidelines.Also applies to: 26-26, 41-41
Line range hint
1-54
: Verify component integration.Let's ensure all template dependencies are properly defined in the component class.
✅ Verification successful
Template dependencies are properly integrated with component
All required methods and properties from the template are correctly defined in the component class:
Properties:
PREDEFINED_IDE: Ide[]
assignedProgrammingLanguages: ProgrammingLanguage[]
remainingProgrammingLanguages: ProgrammingLanguage[]
Methods:
isIdeOfProgrammingLanguage(programmingLanguage: ProgrammingLanguage, ide: Ide)
changeIde(programmingLanguage: ProgrammingLanguage, ide: Ide)
removeProgrammingLanguage(programmingLanguage: ProgrammingLanguage)
addProgrammingLanguage(programmingLanguage: ProgrammingLanguage)
The presence of unit tests in
ide-settings.component.spec.ts
further confirms the proper implementation and testing of these dependencies.🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash # Description: Verify that all methods and properties used in the template are defined in the component # Test: Search for required method and property definitions ast-grep --pattern 'class IdeSettingsComponent { $$$ PREDEFINED_IDE$_ $$$ }' ast-grep --pattern 'class IdeSettingsComponent { $$$ isIdeOfProgrammingLanguage($_) { $$$ } $$$ }' ast-grep --pattern 'class IdeSettingsComponent { $$$ changeIde($_) { $$$ } $$$ }' ast-grep --pattern 'class IdeSettingsComponent { $$$ removeProgrammingLanguage($_) { $$$ } $$$ }' ast-grep --pattern 'class IdeSettingsComponent { $$$ addProgrammingLanguage($_) { $$$ } $$$ }'Length of output: 1134
Script:
#!/bin/bash # Let's try a different approach using ripgrep to find the component file and its contents # First, find the component file fd -e ts "ide-settings.component.ts" # Then let's examine its contents rg -A 5 "export class IdeSettingsComponent" --type ts # Look for specific method definitions rg "isIdeOfProgrammingLanguage|changeIde|removeProgrammingLanguage|addProgrammingLanguage|PREDEFINED_IDE" --type ts # Look for property definitions rg "assignedProgrammingLanguages|remainingProgrammingLanguages" --type tsLength of output: 5527
src/test/javascript/spec/service/settings/ide-settings.service.spec.ts (1)
Line range hint
1-36
: LGTM! Well-structured test setup.The test setup follows best practices with:
- Proper HTTP request mocking
- Clean teardown after each test
- Focused imports
src/main/webapp/app/shared/user-settings/ide-preferences/ide-settings.component.ts (4)
Line range hint
1-14
: LGTM! Well-structured component setup.The component follows Angular best practices with OnPush change detection and proper lifecycle hook implementations. The use of signals shows good adoption of modern Angular features.
26-31
: LGTM! Good subscription management and initialization.The subscription management and constructor initialization follow best practices:
- Proper subscription object initialization
- Service injection and initialization in constructor
- Using signals for reactive state management
53-55
: LGTM! Proper cleanup implementation.The ngOnDestroy implementation correctly handles subscription cleanup, preventing memory leaks.
Line range hint
1-67
: Verify performance improvement in the scores page.The changes align well with the PR objectives by implementing a subscription-based approach for IDE preferences. To verify the performance improvement:
src/test/javascript/spec/component/settings/ide/ide-settings.component.spec.ts (1)
37-57
:⚠️ Potential issueAdd subscription cleanup to prevent memory leaks.
The test should clean up any subscriptions to prevent memory leaks during testing.
Add subscription management:
describe('IdeSettingsComponent', () => { + let subscriptions: Subscription[] = []; + + afterEach(() => { + subscriptions.forEach(sub => sub.unsubscribe()); + subscriptions = []; + }); it('should load predefined IDEs and IDE preferences on init', () => { // ... existing test code ... - component.ngOnInit(); + subscriptions.push( + // Capture the subscription + component.ngOnInit() + ); // ... rest of the test ... }); });Likely invalid or redundant comment.
src/main/webapp/app/shared/components/code-button/code-button.component.html (2)
15-15
: LGTM: Event binding optimizes IDE preferences fetching.The addition of
(shown)="onShow()"
aligns with the PR objective to prevent multiple unnecessary requests for IDE preferences. This ensures the preferences are only fetched when the button/popover is actually shown to the user.
Line range hint
19-91
: LGTM: Template follows new Angular syntax guidelines.The template consistently uses the new
@if
syntax instead of*ngIf
throughout all conditional blocks, which aligns with the coding guidelines.src/main/webapp/app/shared/components/code-button/code-button.component.ts (1)
1-1
: LGTM: Proper lifecycle hook implementationThe addition of OnDestroy and Subscription imports, along with implementing the OnDestroy interface, follows Angular best practices for subscription management.
Also applies to: 19-19, 26-26
it('should load IDE preferences', () => { | ||
const mockIdeMappingDTO: IdeMappingDTO[] = [ | ||
{ programmingLanguage: ProgrammingLanguage.JAVA, ide: { name: 'VS Code', deepLink: 'vscode://vscode.git/clone?url={cloneUrl}' } }, | ||
]; | ||
const expectedMap = new Map<ProgrammingLanguage, Ide>([[ProgrammingLanguage.JAVA, { name: 'VS Code', deepLink: 'vscode://vscode.git/clone?url={cloneUrl}' }]]); | ||
|
||
service.loadIdePreferences().then((ideMap) => { | ||
service.loadIdePreferences().subscribe((ideMap) => { | ||
expect(ideMap).toEqual(expectedMap); | ||
}); | ||
|
||
tick(); | ||
|
||
const req = httpMock.expectOne(service.ideSettingsUrl); | ||
expect(req.request.method).toBe('GET'); | ||
req.flush(mockIdeMappingDTO); | ||
})); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance test coverage and expectations specificity.
While the basic test flow is correct, consider these improvements:
- Use more specific expectations as per guidelines
- Add error case testing
- Verify subscription completion
Here's how to improve the test:
it('should load IDE preferences', () => {
const mockIdeMappingDTO: IdeMappingDTO[] = [
{ programmingLanguage: ProgrammingLanguage.JAVA, ide: { name: 'VS Code', deepLink: 'vscode://vscode.git/clone?url={cloneUrl}' } },
];
const expectedMap = new Map<ProgrammingLanguage, Ide>([[ProgrammingLanguage.JAVA, { name: 'VS Code', deepLink: 'vscode://vscode.git/clone?url={cloneUrl}' }]]);
- service.loadIdePreferences().subscribe((ideMap) => {
- expect(ideMap).toEqual(expectedMap);
- });
+ let completed = false;
+ service.loadIdePreferences().subscribe({
+ next: (ideMap) => {
+ expect(ideMap).toContainEntries([...expectedMap.entries()]);
+ expect(ideMap.size).toBe(1);
+ expect(ideMap.get(ProgrammingLanguage.JAVA)).toEqual(expectedMap.get(ProgrammingLanguage.JAVA));
+ },
+ complete: () => { completed = true; }
+ });
const req = httpMock.expectOne(service.ideSettingsUrl);
expect(req.request.method).toBe('GET');
req.flush(mockIdeMappingDTO);
+ expect(completed).toBeTrue();
});
+it('should handle error when loading IDE preferences', () => {
+ service.loadIdePreferences().subscribe({
+ error: (error) => {
+ expect(error.status).toBe(500);
+ }
+ });
+
+ const req = httpMock.expectOne(service.ideSettingsUrl);
+ req.flush('Error', { status: 500, statusText: 'Server Error' });
+});
📝 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.
it('should load IDE preferences', () => { | |
const mockIdeMappingDTO: IdeMappingDTO[] = [ | |
{ programmingLanguage: ProgrammingLanguage.JAVA, ide: { name: 'VS Code', deepLink: 'vscode://vscode.git/clone?url={cloneUrl}' } }, | |
]; | |
const expectedMap = new Map<ProgrammingLanguage, Ide>([[ProgrammingLanguage.JAVA, { name: 'VS Code', deepLink: 'vscode://vscode.git/clone?url={cloneUrl}' }]]); | |
service.loadIdePreferences().then((ideMap) => { | |
service.loadIdePreferences().subscribe((ideMap) => { | |
expect(ideMap).toEqual(expectedMap); | |
}); | |
tick(); | |
const req = httpMock.expectOne(service.ideSettingsUrl); | |
expect(req.request.method).toBe('GET'); | |
req.flush(mockIdeMappingDTO); | |
})); | |
}); | |
it('should load IDE preferences', () => { | |
const mockIdeMappingDTO: IdeMappingDTO[] = [ | |
{ programmingLanguage: ProgrammingLanguage.JAVA, ide: { name: 'VS Code', deepLink: 'vscode://vscode.git/clone?url={cloneUrl}' } }, | |
]; | |
const expectedMap = new Map<ProgrammingLanguage, Ide>([[ProgrammingLanguage.JAVA, { name: 'VS Code', deepLink: 'vscode://vscode.git/clone?url={cloneUrl}' }]]); | |
let completed = false; | |
service.loadIdePreferences().subscribe({ | |
next: (ideMap) => { | |
expect(ideMap).toContainEntries([...expectedMap.entries()]); | |
expect(ideMap.size).toBe(1); | |
expect(ideMap.get(ProgrammingLanguage.JAVA)).toEqual(expectedMap.get(ProgrammingLanguage.JAVA)); | |
}, | |
complete: () => { completed = true; } | |
}); | |
const req = httpMock.expectOne(service.ideSettingsUrl); | |
expect(req.request.method).toBe('GET'); | |
req.flush(mockIdeMappingDTO); | |
expect(completed).toBeTrue(); | |
}); | |
it('should handle error when loading IDE preferences', () => { | |
service.loadIdePreferences().subscribe({ | |
error: (error) => { | |
expect(error.status).toBe(500); | |
} | |
}); | |
const req = httpMock.expectOne(service.ideSettingsUrl); | |
req.flush('Error', { status: 500, statusText: 'Server Error' }); | |
}); |
addProgrammingLanguage(programmingLanguage: ProgrammingLanguage) { | ||
this.ideSettingsService.saveIdePreference(programmingLanguage, this.PREDEFINED_IDE[0]).subscribe(); | ||
} | ||
|
||
changeIde(programmingLanguage: ProgrammingLanguage, ide: Ide) { | ||
this.ideSettingsService.saveIdePreference(programmingLanguage, ide).subscribe((ide) => { | ||
this.programmingLanguageToIde.update((map) => new Map(map.set(programmingLanguage, ide))); | ||
}); | ||
this.ideSettingsService.saveIdePreference(programmingLanguage, ide).subscribe(); | ||
} | ||
|
||
removeProgrammingLanguage(programmingLanguage: ProgrammingLanguage) { | ||
this.ideSettingsService.deleteIdePreference(programmingLanguage).subscribe(() => { | ||
const programmingLanguageToIdeMap: Map<ProgrammingLanguage, Ide> = new Map(this.programmingLanguageToIde()); | ||
programmingLanguageToIdeMap.delete(programmingLanguage); | ||
|
||
this.programmingLanguageToIde.set(programmingLanguageToIdeMap); | ||
|
||
this.remainingProgrammingLanguages.push(programmingLanguage); | ||
this.assignedProgrammingLanguages = this.assignedProgrammingLanguages.filter((x) => x !== programmingLanguage); | ||
}); | ||
this.ideSettingsService.deleteIdePreference(programmingLanguage).subscribe(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Optimize service method subscriptions.
The current implementation has empty subscriptions and lacks error handling. Consider using more efficient approaches.
-addProgrammingLanguage(programmingLanguage: ProgrammingLanguage) {
- this.ideSettingsService.saveIdePreference(programmingLanguage, this.PREDEFINED_IDE[0]).subscribe();
+addProgrammingLanguage(programmingLanguage: ProgrammingLanguage) {
+ this.ideSettingsService.saveIdePreference(programmingLanguage, this.PREDEFINED_IDE[0]).subscribe({
+ error: (error) => {
+ console.error('Failed to save IDE preference:', error);
+ // Consider showing a user-friendly error message
+ }
+ });
}
-changeIde(programmingLanguage: ProgrammingLanguage, ide: Ide) {
- this.ideSettingsService.saveIdePreference(programmingLanguage, ide).subscribe();
+changeIde(programmingLanguage: ProgrammingLanguage, ide: Ide) {
+ this.ideSettingsService.saveIdePreference(programmingLanguage, ide).subscribe({
+ error: (error) => {
+ console.error('Failed to change IDE:', error);
+ // Consider showing a user-friendly error message
+ }
+ });
}
-removeProgrammingLanguage(programmingLanguage: ProgrammingLanguage) {
- this.ideSettingsService.deleteIdePreference(programmingLanguage).subscribe();
+removeProgrammingLanguage(programmingLanguage: ProgrammingLanguage) {
+ this.ideSettingsService.deleteIdePreference(programmingLanguage).subscribe({
+ error: (error) => {
+ console.error('Failed to remove IDE preference:', error);
+ // Consider showing a user-friendly error 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.
addProgrammingLanguage(programmingLanguage: ProgrammingLanguage) { | |
this.ideSettingsService.saveIdePreference(programmingLanguage, this.PREDEFINED_IDE[0]).subscribe(); | |
} | |
changeIde(programmingLanguage: ProgrammingLanguage, ide: Ide) { | |
this.ideSettingsService.saveIdePreference(programmingLanguage, ide).subscribe((ide) => { | |
this.programmingLanguageToIde.update((map) => new Map(map.set(programmingLanguage, ide))); | |
}); | |
this.ideSettingsService.saveIdePreference(programmingLanguage, ide).subscribe(); | |
} | |
removeProgrammingLanguage(programmingLanguage: ProgrammingLanguage) { | |
this.ideSettingsService.deleteIdePreference(programmingLanguage).subscribe(() => { | |
const programmingLanguageToIdeMap: Map<ProgrammingLanguage, Ide> = new Map(this.programmingLanguageToIde()); | |
programmingLanguageToIdeMap.delete(programmingLanguage); | |
this.programmingLanguageToIde.set(programmingLanguageToIdeMap); | |
this.remainingProgrammingLanguages.push(programmingLanguage); | |
this.assignedProgrammingLanguages = this.assignedProgrammingLanguages.filter((x) => x !== programmingLanguage); | |
}); | |
this.ideSettingsService.deleteIdePreference(programmingLanguage).subscribe(); | |
} | |
addProgrammingLanguage(programmingLanguage: ProgrammingLanguage) { | |
this.ideSettingsService.saveIdePreference(programmingLanguage, this.PREDEFINED_IDE[0]).subscribe({ | |
error: (error) => { | |
console.error('Failed to save IDE preference:', error); | |
// Consider showing a user-friendly error message | |
} | |
}); | |
} | |
changeIde(programmingLanguage: ProgrammingLanguage, ide: Ide) { | |
this.ideSettingsService.saveIdePreference(programmingLanguage, ide).subscribe({ | |
error: (error) => { | |
console.error('Failed to change IDE:', error); | |
// Consider showing a user-friendly error message | |
} | |
}); | |
} | |
removeProgrammingLanguage(programmingLanguage: ProgrammingLanguage) { | |
this.ideSettingsService.deleteIdePreference(programmingLanguage).subscribe({ | |
error: (error) => { | |
console.error('Failed to remove IDE preference:', error); | |
// Consider showing a user-friendly error message | |
} | |
}); | |
} |
it('should load predefined IDEs and IDE preferences on init', () => { | ||
const predefinedIdes = [ | ||
{ name: 'VS Code', deepLink: 'vscode://vscode.git/clone?url={cloneUrl}' }, | ||
{ name: 'IntelliJ', deepLink: 'jetbrains://idea/checkout/git?idea.required.plugins.id=Git4Idea&checkout.repo={cloneUrl}' }, | ||
]; | ||
const idePreferences = new Map([[ProgrammingLanguage.JAVA, predefinedIdes[0]]]); | ||
const loadedIdePreferences = new Map([ | ||
[ProgrammingLanguage.JAVA, predefinedIdes[0]], | ||
[ProgrammingLanguage.EMPTY, predefinedIdes[0]], | ||
]); | ||
|
||
mockIdeSettingsService.loadPredefinedIdes.mockReturnValue(of(predefinedIdes)); | ||
mockIdeSettingsService.loadIdePreferences.mockReturnValue(Promise.resolve(idePreferences)); | ||
mockIdeSettingsService.loadIdePreferences.mockReturnValue(of(idePreferences)); | ||
|
||
component.ngOnInit(); | ||
|
||
tick(); | ||
|
||
expect(mockIdeSettingsService.loadPredefinedIdes).toHaveBeenCalledOnce(); | ||
expect(mockIdeSettingsService.loadIdePreferences).toHaveBeenCalledOnce(); | ||
expect(component.PREDEFINED_IDE).toEqual(predefinedIdes); | ||
expect(component.programmingLanguageToIde()).toEqual(loadedIdePreferences); | ||
expect(component.programmingLanguageToIde()).toEqual(idePreferences); | ||
expect(component.assignedProgrammingLanguages).toEqual([ProgrammingLanguage.JAVA]); | ||
expect(component.remainingProgrammingLanguages).toEqual( | ||
Object.values(ProgrammingLanguage).filter((x) => x !== ProgrammingLanguage.JAVA && x !== ProgrammingLanguage.EMPTY), | ||
); | ||
})); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance test specificity and structure.
While the test is generally well-structured, there are a few improvements we can make to align with best practices:
- The test should verify the exact structure of the Map entries
- The filtering logic test could be more specific
Consider updating the assertions:
-expect(component.programmingLanguageToIde()).toEqual(idePreferences);
+expect(component.programmingLanguageToIde()).toContainEntries([
+ [ProgrammingLanguage.JAVA, predefinedIdes[0]]
+]);
-expect(component.remainingProgrammingLanguages).toEqual(
- Object.values(ProgrammingLanguage).filter((x) => x !== ProgrammingLanguage.JAVA && x !== ProgrammingLanguage.EMPTY),
-);
+const expectedLanguages = Object.values(ProgrammingLanguage)
+ .filter((x) => x !== ProgrammingLanguage.JAVA && x !== ProgrammingLanguage.EMPTY);
+expect(component.remainingProgrammingLanguages).toEqual(expectedLanguages);
📝 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.
it('should load predefined IDEs and IDE preferences on init', () => { | |
const predefinedIdes = [ | |
{ name: 'VS Code', deepLink: 'vscode://vscode.git/clone?url={cloneUrl}' }, | |
{ name: 'IntelliJ', deepLink: 'jetbrains://idea/checkout/git?idea.required.plugins.id=Git4Idea&checkout.repo={cloneUrl}' }, | |
]; | |
const idePreferences = new Map([[ProgrammingLanguage.JAVA, predefinedIdes[0]]]); | |
const loadedIdePreferences = new Map([ | |
[ProgrammingLanguage.JAVA, predefinedIdes[0]], | |
[ProgrammingLanguage.EMPTY, predefinedIdes[0]], | |
]); | |
mockIdeSettingsService.loadPredefinedIdes.mockReturnValue(of(predefinedIdes)); | |
mockIdeSettingsService.loadIdePreferences.mockReturnValue(Promise.resolve(idePreferences)); | |
mockIdeSettingsService.loadIdePreferences.mockReturnValue(of(idePreferences)); | |
component.ngOnInit(); | |
tick(); | |
expect(mockIdeSettingsService.loadPredefinedIdes).toHaveBeenCalledOnce(); | |
expect(mockIdeSettingsService.loadIdePreferences).toHaveBeenCalledOnce(); | |
expect(component.PREDEFINED_IDE).toEqual(predefinedIdes); | |
expect(component.programmingLanguageToIde()).toEqual(loadedIdePreferences); | |
expect(component.programmingLanguageToIde()).toEqual(idePreferences); | |
expect(component.assignedProgrammingLanguages).toEqual([ProgrammingLanguage.JAVA]); | |
expect(component.remainingProgrammingLanguages).toEqual( | |
Object.values(ProgrammingLanguage).filter((x) => x !== ProgrammingLanguage.JAVA && x !== ProgrammingLanguage.EMPTY), | |
); | |
})); | |
}); | |
it('should load predefined IDEs and IDE preferences on init', () => { | |
const predefinedIdes = [ | |
{ name: 'VS Code', deepLink: 'vscode://vscode.git/clone?url={cloneUrl}' }, | |
{ name: 'IntelliJ', deepLink: 'jetbrains://idea/checkout/git?idea.required.plugins.id=Git4Idea&checkout.repo={cloneUrl}' }, | |
]; | |
const idePreferences = new Map([[ProgrammingLanguage.JAVA, predefinedIdes[0]]]); | |
mockIdeSettingsService.loadPredefinedIdes.mockReturnValue(of(predefinedIdes)); | |
mockIdeSettingsService.loadIdePreferences.mockReturnValue(of(idePreferences)); | |
component.ngOnInit(); | |
expect(mockIdeSettingsService.loadPredefinedIdes).toHaveBeenCalledOnce(); | |
expect(mockIdeSettingsService.loadIdePreferences).toHaveBeenCalledOnce(); | |
expect(component.PREDEFINED_IDE).toEqual(predefinedIdes); | |
expect(component.programmingLanguageToIde()).toContainEntries([ | |
[ProgrammingLanguage.JAVA, predefinedIdes[0]] | |
]); | |
expect(component.assignedProgrammingLanguages).toEqual([ProgrammingLanguage.JAVA]); | |
const expectedLanguages = Object.values(ProgrammingLanguage) | |
.filter((x) => x !== ProgrammingLanguage.JAVA && x !== ProgrammingLanguage.EMPTY); | |
expect(component.remainingProgrammingLanguages).toEqual(expectedLanguages); | |
}); |
public onShow() { | ||
this.ideSettingsService.loadIdePreferences().subscribe(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improve subscription management in onShow method
The current implementation may lead to memory leaks as the subscription isn't managed. Consider adding proper subscription management and error handling.
public onShow() {
- this.ideSettingsService.loadIdePreferences().subscribe();
+ const loadPreferencesSubscription = this.ideSettingsService.loadIdePreferences()
+ .pipe(takeUntil(this.destroy$))
+ .subscribe({
+ error: (error) => console.error('Failed to load IDE preferences:', error)
+ });
+ this.subscription.add(loadPreferencesSubscription);
}
Committable suggestion was skipped due to low confidence.
ngOnDestroy() { | ||
this.subscription.unsubscribe(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Update ngOnDestroy to complete destroy$ Subject
If implementing the suggested destroy$ Subject, ensure it's completed in ngOnDestroy.
ngOnDestroy() {
+ this.destroy$.next();
+ this.destroy$.complete();
this.subscription.unsubscribe();
}
Committable suggestion was skipped due to low confidence.
public loadIdePreferences(): Observable<Map<ProgrammingLanguage, Ide>> { | ||
return this.http.get<IdeMappingDTO[]>(this.ideSettingsUrl).pipe( | ||
map((data) => { | ||
const idePreferences = new Map<ProgrammingLanguage, Ide>(data.map((x) => [x.programmingLanguage, x.ide])); | ||
if (!idePreferences.has(ProgrammingLanguage.EMPTY)) { | ||
idePreferences.set(ProgrammingLanguage.EMPTY, this.predefinedIdes[0]); | ||
} | ||
this.idePreferencesSubject.next(idePreferences); | ||
return idePreferences; | ||
}), | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add error handling for 'loadIdePreferences' HTTP request
Currently, there is no error handling in the loadIdePreferences()
method. Without error handling, any HTTP errors could lead to unhandled exceptions or silent failures. Consider adding error handling to manage such cases gracefully.
Would you like assistance in adding error handling to this method?
if (!idePreferences.has(ProgrammingLanguage.EMPTY)) { | ||
idePreferences.set(ProgrammingLanguage.EMPTY, this.predefinedIdes[0]); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure consistent use of the fallback IDE
Using this.predefinedIdes[0]
assumes that the first element is always the fallback IDE. If the predefined IDEs loaded from the server do not include the fallback IDE or the order changes, this assumption may not hold.
Apply this diff to use fallbackIde
directly:
- idePreferences.set(ProgrammingLanguage.EMPTY, this.predefinedIdes[0]);
+ idePreferences.set(ProgrammingLanguage.EMPTY, IdeSettingsService.fallbackIde);
📝 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.
if (!idePreferences.has(ProgrammingLanguage.EMPTY)) { | |
idePreferences.set(ProgrammingLanguage.EMPTY, this.predefinedIdes[0]); | |
} | |
if (!idePreferences.has(ProgrammingLanguage.EMPTY)) { | |
idePreferences.set(ProgrammingLanguage.EMPTY, IdeSettingsService.fallbackIde); | |
} |
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http'; | ||
import { Ide, IdeMappingDTO } from 'app/shared/user-settings/ide-preferences/ide.model'; | ||
import { ProgrammingLanguage } from 'app/entities/programming/programming-exercise.model'; | ||
|
||
@Injectable({ providedIn: 'root' }) | ||
export class IdeSettingsService { | ||
public readonly ideSettingsUrl = 'api/ide-settings'; | ||
public ideSettingsUrl = 'api/ide-settings'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider making 'ideSettingsUrl' readonly again
The ideSettingsUrl
property does not appear to be modified after its initial assignment. Keeping it readonly
can prevent unintended changes.
Apply this diff to make ideSettingsUrl
readonly:
- public ideSettingsUrl = 'api/ide-settings';
+ public readonly ideSettingsUrl = 'api/ide-settings';
📝 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.
public ideSettingsUrl = 'api/ide-settings'; | |
public readonly ideSettingsUrl = 'api/ide-settings'; |
map((response) => { | ||
const prev = new Map(this.idePreferencesSubject.getValue()); | ||
prev.delete(programmingLanguage); | ||
this.idePreferencesSubject.next(prev); | ||
return response; | ||
}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use 'tap' operator for side effects in 'deleteIdePreference'
Similar to saveIdePreference
, side effects in deleteIdePreference
should be placed within a tap
operator to align with best practices.
Apply this diff to use tap
for the side effect:
return this.http.delete<void>(this.ideSettingsUrl, { params, observe: 'response' }).pipe(
- map((response) => {
+ tap(() => {
const prev = new Map(this.idePreferencesSubject.getValue());
prev.delete(programmingLanguage);
this.idePreferencesSubject.next(prev);
- return response;
- }),
+ }),
);
📝 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.
map((response) => { | |
const prev = new Map(this.idePreferencesSubject.getValue()); | |
prev.delete(programmingLanguage); | |
this.idePreferencesSubject.next(prev); | |
return response; | |
}), | |
tap(() => { | |
const prev = new Map(this.idePreferencesSubject.getValue()); | |
prev.delete(programmingLanguage); | |
this.idePreferencesSubject.next(prev); | |
}), |
const prev = new Map(this.idePreferencesSubject.getValue()); | ||
prev.set(programmingLanguage, res.ide); | ||
this.idePreferencesSubject.next(prev); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Use 'tap' operator for side effects in 'saveIdePreference'
When performing side effects like updating idePreferencesSubject
, it's more appropriate to use the tap
operator instead of map
. The map
operator should be reserved for transforming values.
Apply this diff to use tap
for the side effect:
return this.http.put<IdeMappingDTO>(this.ideSettingsUrl, ide, { params }).pipe(
- map((res) => {
+ tap((res) => {
const prev = new Map(this.idePreferencesSubject.getValue());
prev.set(programmingLanguage, res.ide);
this.idePreferencesSubject.next(prev);
- return res.ide;
- }),
+ }),
+ map((res) => res.ide),
);
📝 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.
const prev = new Map(this.idePreferencesSubject.getValue()); | |
prev.set(programmingLanguage, res.ide); | |
this.idePreferencesSubject.next(prev); | |
return this.http.put<IdeMappingDTO>(this.ideSettingsUrl, ide, { params }).pipe( | |
tap((res) => { | |
const prev = new Map(this.idePreferencesSubject.getValue()); | |
prev.set(programmingLanguage, res.ide); | |
this.idePreferencesSubject.next(prev); | |
}), | |
map((res) => res.ide), | |
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested on TS3. When I clicked on the Scores button, it seems that no fetch request for ide-settings was made. After clicking on the code button, one fetch request for ide-settings was made, and another one (xhr)
Exercises._.Practical.Course_.Interactive.Learning.WS24_25.-.Google.Chrome.2024-10-26.21-32-44.mp4
protected readonly ProgrammingLanguage = ProgrammingLanguage; | ||
protected readonly faPlus = faPlus; | ||
protected readonly faTrash = faTrash; | ||
PREDEFINED_IDE: Ide[] = [{ name: 'VS Code', deepLink: 'vscode://vscode.git/clone?url={cloneUrl}' }]; | ||
PREDEFINED_IDE: Ide[]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we shouldn't use SCREAMING_SNAKE_CASE here, as it's not a constant.
https://docs.artemis.cit.tum.de/dev/guidelines/server.html#naming-convention
return this.http.get<Ide[]>(this.ideSettingsUrl + '/predefined').pipe( | ||
map((ides) => { | ||
this.predefinedIdes = ides; | ||
return ides; | ||
}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should use tap instead of map here, since you return the exact input anyway. This way you can get rid of the return
return this.http.delete<void>(this.ideSettingsUrl, { params, observe: 'response' }).pipe( | ||
map((response) => { | ||
const prev = new Map(this.idePreferencesSubject.getValue()); | ||
prev.delete(programmingLanguage); | ||
this.idePreferencesSubject.next(prev); | ||
return response; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here, you can also just use tap
, even without the response parameter, since you don't interact with it here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is a screenshot of my network traffic after I followed the instructions.The first 2 with ide-settings were after i clicked scores and the second two were after i clicked the code button.This behavior is consistent with the fix: a single initial request on loading the scores page, followed by additional requests only when an action (like clicking "Code") explicitly triggers them. I hope this helps! Have a nice day!
Checklist
General
Client
authorities
to all new routes and checked the course groups for displaying navigation elements (links, buttons).Changes affecting Programming Exercises
Description
Fixes an issue where request to fetch ide preferences on the scores page where send per code button. This closes #9559.
In addition display tooltip for intelliJ IDEs directly and not in the question mark icon. Closes #9541
Steps for Testing
Prerequisites:
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
Manual Tests
Exam Mode Test
Performance Tests
Test Coverage
Screenshots
Summary by CodeRabbit
Release Notes
New Features
CodeButtonComponent
through a new event binding.IdeSettingsComponent
.Bug Fixes
CodeButtonComponent
andIdeSettingsComponent
.Style
Documentation