Skip to content

Commit 8880242

Browse files
Merge pull request #508 from frankframework/fix/frontend-architecture
2 parents 93336ea + ead04ec commit 8880242

File tree

43 files changed

+554
-579
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+554
-579
lines changed

src/main/frontend/src/app/app.component.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,13 @@ export class AppComponent implements OnInit {
4242
ngOnInit(): void {
4343
const wasExtended = this.graphStateService.restoreAndClearOAuthExtended();
4444

45-
this.authService.checkAuthStatus().subscribe();
45+
this.authService.checkAuthStatus().subscribe({
46+
next: (user) => {
47+
if (user) {
48+
this.authService.setAuthenticated(user);
49+
}
50+
},
51+
});
4652

4753
this.route.queryParams.subscribe((parameters) => {
4854
const currentUrl = this.router.url;

src/main/frontend/src/app/app.routes.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,22 @@ import { FrankFrameworkMemberGuard } from './guards/frankframework-member.guard'
1010

1111
export const routes: Routes = [
1212
{ path: '', redirectTo: 'graph', pathMatch: 'full' },
13-
{ path: 'graph', component: ReleaseGraphComponent },
14-
{ path: 'graph/:id', component: ReleaseDetailsComponent },
15-
{ path: 'roadmap', component: ReleaseRoadmapComponent },
16-
{ path: 'release-manage/:id', component: ReleaseManageComponent, canActivate: [FrankFrameworkMemberGuard] },
1713
{
18-
path: 'release-manage/:id/business-values',
19-
component: BusinessValueManageComponent,
20-
canActivate: [FrankFrameworkMemberGuard],
14+
path: 'graph',
15+
children: [
16+
{ path: '', component: ReleaseGraphComponent },
17+
{ path: ':id', component: ReleaseDetailsComponent },
18+
],
2119
},
20+
{ path: 'roadmap', component: ReleaseRoadmapComponent },
2221
{
23-
path: 'release-manage/:id/vulnerabilities',
24-
component: VulnerabilityImpactManageComponent,
22+
path: 'release-manage/:id',
2523
canActivate: [FrankFrameworkMemberGuard],
24+
children: [
25+
{ path: '', component: ReleaseManageComponent },
26+
{ path: 'business-values', component: BusinessValueManageComponent },
27+
{ path: 'vulnerabilities', component: VulnerabilityImpactManageComponent },
28+
],
2629
},
2730
{ path: 'not-found', component: NotFoundComponent },
2831
{ path: '**', redirectTo: 'not-found' },

src/main/frontend/src/app/app.service.spec.ts

Lines changed: 0 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -24,98 +24,6 @@ describe('AppService', () => {
2424
expect(service).toBeTruthy();
2525
});
2626

27-
describe('get()', () => {
28-
const testUrl = 'test-endpoint';
29-
const mockData = { message: 'success' };
30-
31-
it('should make a GET request with parameters', () => {
32-
const parameters = { name: 'frank', version: 8 };
33-
34-
service.get(testUrl, parameters).subscribe((data) => {
35-
expect(data).toEqual(mockData);
36-
});
37-
38-
const request = httpMock.expectOne((r) => r.url === testUrl);
39-
40-
expect(request.request.method).toBe('GET');
41-
expect(request.request.params.get('name')).toBe('frank');
42-
expect(request.request.params.get('version')).toBe('8');
43-
request.flush(mockData);
44-
});
45-
});
46-
47-
describe('post()', () => {
48-
const testUrl = 'test-endpoint';
49-
const mockBody = { data: 'test' };
50-
const mockResponse = { id: 1 };
51-
52-
it('should make a POST request', () => {
53-
service.post(testUrl, mockBody).subscribe((data) => {
54-
expect(data).toEqual(mockResponse);
55-
});
56-
57-
const request = httpMock.expectOne(testUrl);
58-
59-
expect(request.request.method).toBe('POST');
60-
expect(request.request.body).toEqual(mockBody);
61-
request.flush(mockResponse);
62-
});
63-
64-
it('should support custom headers', () => {
65-
const headers = { 'X-Custom': 'Value' };
66-
service.post(testUrl, mockBody, headers).subscribe();
67-
68-
const request = httpMock.expectOne(testUrl);
69-
70-
expect(request.request.headers.get('X-Custom')).toBe('Value');
71-
request.flush(mockResponse);
72-
});
73-
});
74-
75-
describe('put()', () => {
76-
const testUrl = 'test-endpoint/1';
77-
const mockBody = { data: 'updated' };
78-
const mockResponse = { id: 1, data: 'updated' };
79-
80-
it('should make a PUT request', () => {
81-
service.put(testUrl, mockBody).subscribe((data) => {
82-
expect(data).toEqual(mockResponse);
83-
});
84-
85-
const request = httpMock.expectOne(testUrl);
86-
87-
expect(request.request.method).toBe('PUT');
88-
expect(request.request.body).toEqual(mockBody);
89-
request.flush(mockResponse);
90-
});
91-
});
92-
93-
describe('delete()', () => {
94-
const testUrl = 'test-endpoint/1';
95-
96-
it('should make a DELETE request', () => {
97-
service.delete(testUrl).subscribe((data) => {
98-
expect(data).toBeNull();
99-
});
100-
101-
const request = httpMock.expectOne(testUrl);
102-
103-
expect(request.request.method).toBe('DELETE');
104-
request.flush(null);
105-
});
106-
107-
it('should support custom headers in DELETE', () => {
108-
const headers = { 'X-Auth': 'Token' };
109-
service.delete(testUrl, headers).subscribe();
110-
111-
const request = httpMock.expectOne(testUrl);
112-
113-
expect(request.request.method).toBe('DELETE');
114-
expect(request.request.headers.get('X-Auth')).toBe('Token');
115-
request.flush(null);
116-
});
117-
});
118-
11927
describe('createAPIUrl()', () => {
12028
it('should correctly construct an API URL', () => {
12129
expect(service.createAPIUrl('users')).toBe('/api/users');

src/main/frontend/src/app/app.service.ts

Lines changed: 1 addition & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import { Injectable, inject } from '@angular/core';
2-
import { HttpClient, HttpParams } from '@angular/common/http';
3-
import { Observable } from 'rxjs';
1+
import { Injectable } from '@angular/core';
42

53
export const GitHubStates = {
64
OPEN: 'OPEN',
@@ -13,82 +11,6 @@ export type GitHubState = (typeof GitHubStates)[keyof typeof GitHubStates];
1311
providedIn: 'root',
1412
})
1513
export class AppService {
16-
private http = inject(HttpClient);
17-
18-
/**
19-
* Performs a GET request to the given URL with optional query parameters.
20-
*
21-
* @template T - The expected response type.
22-
* @param url - The API endpoint URL.
23-
* @param parameters - Optional query parameters as key-value pairs. Values can be primitive types or HTTP options.
24-
* @returns An Observable of type `T` containing the response data.
25-
*/
26-
public get<T>(url: string, parameters?: Record<string, string | number | boolean>): Observable<T> {
27-
let httpParameters = new HttpParams();
28-
29-
if (parameters) {
30-
for (const key of Object.keys(parameters)) {
31-
const value = parameters[key];
32-
33-
if (value !== undefined && value !== null && value !== '') {
34-
httpParameters = httpParameters.set(key, String(value));
35-
}
36-
}
37-
}
38-
39-
return this.http.get<T>(url, { params: httpParameters });
40-
}
41-
42-
/**
43-
* Performs a POST request to the given URL with optional body and options.
44-
*
45-
* @template T - The expected response type.
46-
* @template B - The request body type.
47-
* @param url - The API endpoint URL.
48-
* @param body - Optional request body.
49-
* @param options - Optional HTTP headers as key-value pairs.
50-
* @returns An Observable of type `T` containing the response data.
51-
*/
52-
public post<T, B = unknown>(url: string, body?: B, options?: Record<string, string>): Observable<T> {
53-
const httpOptions: { headers?: Record<string, string> } = {};
54-
55-
if (options && Object.keys(options).length > 0) {
56-
httpOptions.headers = options;
57-
}
58-
59-
return this.http.post<T>(url, body ?? null, httpOptions);
60-
}
61-
62-
/**
63-
* Performs a PUT request to the given URL with optional body and options.
64-
*
65-
* @template T - The expected response type.
66-
* @template B - The request body type.
67-
* @param url - The API endpoint URL.
68-
* @param body - Optional request body.
69-
* @param options - Optional HTTP headers as key-value pairs.
70-
* @returns An Observable of type `T` containing the response data.
71-
*/
72-
public put<T, B = unknown>(url: string, body?: B, options?: Record<string, string>): Observable<T> {
73-
const httpOptions: { headers?: Record<string, string> } = {};
74-
75-
if (options && Object.keys(options).length > 0) {
76-
httpOptions.headers = options;
77-
}
78-
79-
return this.http.put<T>(url, body ?? null, httpOptions);
80-
}
81-
82-
public delete<T>(url: string, options?: Record<string, string>): Observable<T> {
83-
const httpOptions: { headers?: Record<string, string> } = {};
84-
85-
if (options && Object.keys(options).length > 0) {
86-
httpOptions.headers = options;
87-
}
88-
89-
return this.http.delete<T>(url, httpOptions);
90-
}
91-
9214
/**
9315
* Constructs a qualified API URL by appending a given endpoint to the base URL.
9416
*

src/main/frontend/src/app/pages/header/header.component.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ <h3>Roadmap</h3>
2323
</li>
2424
</ul>
2525

26-
<div class="auth-section">
26+
<section class="auth-section">
2727
@if (authService.isAuthenticated()) {
2828
<div
2929
class="user-profile"
@@ -37,8 +37,8 @@ <h3>Roadmap</h3>
3737
<span class="username">{{ authService.currentUser()?.username }}</span>
3838

3939
@if (showUserMenu) {
40-
<div class="user-menu" tabindex="0" (click)="$event.stopPropagation()">
41-
<div class="user-menu-header">
40+
<menu class="user-menu" tabindex="0" (click)="$event.stopPropagation()">
41+
<header class="user-menu-header">
4242
<img
4343
class="avatar-large"
4444
[src]="authService.currentUser()?.avatarUrl"
@@ -48,7 +48,7 @@ <h3>Roadmap</h3>
4848
<span class="username-large">{{ authService.currentUser()?.username }}</span>
4949
<span class="user-id">ID: {{ authService.currentUser()?.githubId }}</span>
5050
</div>
51-
</div>
51+
</header>
5252
<div class="user-menu-divider"></div>
5353
<button class="user-menu-item logout-btn" (click)="onLogout()">
5454
<svg height="16" width="16" viewBox="0 0 16 16" fill="currentColor">
@@ -58,7 +58,7 @@ <h3>Roadmap</h3>
5858
</svg>
5959
Logout
6060
</button>
61-
</div>
61+
</menu>
6262
}
6363
</div>
6464
} @else {
@@ -77,7 +77,7 @@ <h3>Roadmap</h3>
7777
}
7878
</button>
7979
}
80-
</div>
80+
</section>
8181
</nav>
8282

8383
@if (authService.authError()) {

src/main/frontend/src/app/pages/release-details/release-business-value-modal/release-business-value-modal.component.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
<div class="business-value-modal-content">
33
@if (businessValue()) {
44
<div class="business-value-details">
5-
<div class="description-section">
5+
<section class="description-section">
66
<h3>Description</h3>
77
<p class="description-text">{{ businessValue()!.description }}</p>
8-
</div>
8+
</section>
99

10-
<div class="issues-section">
10+
<section class="issues-section">
1111
<div class="issues-header">
1212
<h3>Connected Issues</h3>
1313
<span class="issue-count"
@@ -37,7 +37,7 @@ <h3>Connected Issues</h3>
3737
<p>No issues connected to this business value.</p>
3838
</div>
3939
}
40-
</div>
40+
</section>
4141
</div>
4242
}
4343
</div>

src/main/frontend/src/app/pages/release-details/release-business-value/release-business-value.component.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Component, Input, OnChanges, SimpleChanges, inject, signal } from '@ang
22
import { CommonModule } from '@angular/common';
33
import { BusinessValue, BusinessValueService } from '../../../services/business-value.service';
44
import { ReleaseBusinessValueModalComponent } from '../release-business-value-modal/release-business-value-modal.component';
5-
import { catchError, of } from 'rxjs';
5+
import { catchError, finalize, of } from 'rxjs';
66

77
@Component({
88
selector: 'app-release-business-value',
@@ -45,10 +45,8 @@ export class ReleaseBusinessValueComponent implements OnChanges {
4545
console.error('Failed to load business values:', error);
4646
return of([]);
4747
}),
48+
finalize(() => this.isLoadingBusinessValues.set(false)),
4849
)
49-
.subscribe((businessValues) => {
50-
this.businessValues.set(businessValues);
51-
this.isLoadingBusinessValues.set(false);
52-
});
50+
.subscribe((businessValues) => this.businessValues.set(businessValues));
5351
}
5452
}

src/main/frontend/src/app/pages/release-details/release-details.component.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<div class="release-details-container">
22
@if (!isLoading && release) {
3-
<div class="release-details-header">
3+
<header class="release-details-header">
44
<div class="header-actions">
55
<button class="back-button" (click)="goBack()">
66
<img src="assets/icons/chevron-left.svg" alt="" width="20" height="20" />
@@ -14,11 +14,11 @@
1414
<h2>{{ release.name }}</h2>
1515
<span>{{ release.publishedAt | date: 'longDate' }}</span>
1616
</div>
17-
</div>
17+
</header>
1818

1919
<div class="release-details-body">
2020
<div class="release-details-content">
21-
<div class="left-panel">
21+
<section class="left-panel">
2222
@if (highlightedLabels === undefined && releaseIssues === undefined) {
2323
<div class="no-new-features">
2424
<p>No new features found towards previous release.</p>
@@ -56,17 +56,17 @@ <h2>{{ release.name }}</h2>
5656
</div>
5757
}
5858
}
59-
</div>
59+
</section>
6060

61-
<div class="right-panel">
61+
<section class="right-panel">
6262
@if (vulnerabilities !== undefined) {
6363
<app-release-vulnerabilities [vulnerabilities]="vulnerabilities" [lastScanned]="release.lastScanned" />
6464
} @else {
6565
<div class="placeholder">
6666
<p>Loading vulnerability information...</p>
6767
</div>
6868
}
69-
</div>
69+
</section>
7070
</div>
7171
</div>
7272
} @else if (isLoading) {

0 commit comments

Comments
 (0)