From 85e2def5820acf1e9df5006f97c3995fdda1cfc4 Mon Sep 17 00:00:00 2001 From: clean-coder Date: Tue, 22 Oct 2024 14:28:20 +0200 Subject: [PATCH] client part for deleting a user --- .../src/app/services/user.service.spec.ts | 29 ++- frontend/src/app/services/user.service.ts | 13 ++ .../alert-dialog/alert-dialog.component.html | 29 +++ .../alert-dialog/alert-dialog.component.scss | 0 .../alert-dialog.component.spec.ts | 48 +++++ .../alert-dialog/alert-dialog.component.ts | 24 +++ frontend/src/app/shared/shared.module.ts | 2 + frontend/src/app/shared/testData.ts | 15 ++ .../src/app/shared/types/model/UserOkrData.ts | 10 + .../delete-user/delete-user.component.html | 14 ++ .../delete-user/delete-user.component.scss | 9 + .../delete-user/delete-user.component.spec.ts | 185 ++++++++++++++++++ .../delete-user/delete-user.component.ts | 137 +++++++++++++ .../member-detail.component.html | 3 + .../member-detail.component.spec.ts | 4 +- .../member-detail/member-detail.component.ts | 27 ++- .../team-management/team-management.module.ts | 2 + 17 files changed, 541 insertions(+), 10 deletions(-) create mode 100644 frontend/src/app/shared/dialog/alert-dialog/alert-dialog.component.html create mode 100644 frontend/src/app/shared/dialog/alert-dialog/alert-dialog.component.scss create mode 100644 frontend/src/app/shared/dialog/alert-dialog/alert-dialog.component.spec.ts create mode 100644 frontend/src/app/shared/dialog/alert-dialog/alert-dialog.component.ts create mode 100644 frontend/src/app/shared/types/model/UserOkrData.ts create mode 100644 frontend/src/app/team-management/delete-user/delete-user.component.html create mode 100644 frontend/src/app/team-management/delete-user/delete-user.component.scss create mode 100644 frontend/src/app/team-management/delete-user/delete-user.component.spec.ts create mode 100644 frontend/src/app/team-management/delete-user/delete-user.component.ts diff --git a/frontend/src/app/services/user.service.spec.ts b/frontend/src/app/services/user.service.spec.ts index 852129736f..51722cabbc 100644 --- a/frontend/src/app/services/user.service.spec.ts +++ b/frontend/src/app/services/user.service.spec.ts @@ -1,4 +1,4 @@ -import { fakeAsync, getTestBed, TestBed, tick } from '@angular/core/testing'; +import { fakeAsync, TestBed, tick } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { UserService } from './user.service'; import { testUser, users } from '../shared/testData'; @@ -6,15 +6,12 @@ import { testUser, users } from '../shared/testData'; describe('UserService', () => { let service: UserService; let httpMock: HttpTestingController; - const URL = 'api/v1/users'; - beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], }); service = TestBed.inject(UserService); - const injector = getTestBed(); - httpMock = injector.get(HttpTestingController); + httpMock = TestBed.inject(HttpTestingController); }); afterEach(() => { @@ -29,7 +26,7 @@ describe('UserService', () => { const spy = jest.spyOn(service, 'reloadUsers'); service.getUsers().subscribe(() => { expect(spy).toBeCalledTimes(1); - httpMock.expectOne(URL); + httpMock.expectOne('api/v1/users'); service.getUsers().subscribe((users) => { expect(spy).toBeCalledTimes(1); expect(users).toStrictEqual([]); @@ -75,4 +72,24 @@ describe('UserService', () => { const req2 = httpMock.expectOne(`api/v1/users`); req2.flush({}); })); + + it('getUserOkrData() should call userokrdata', fakeAsync(() => { + service.getUserOkrData(testUser).subscribe(); + const req = httpMock.expectOne(`api/v1/users/${testUser.id}/userokrdata`); + expect(req.request.method).toBe('GET'); + req.flush(testUser); + })); + + it('deleteUser() should call deleteUser and reloadUsers', fakeAsync(() => { + service.deleteUser(testUser).subscribe(); + const reqDeleteUser = httpMock.expectOne(`api/v1/users/${testUser.id}`); + expect(reqDeleteUser.request.method).toBe('DELETE'); + reqDeleteUser.flush(testUser); + + tick(); + + const reqReloadUsers = httpMock.expectOne(`api/v1/users`); + expect(reqReloadUsers.request.method).toBe('GET'); + reqReloadUsers.flush({}); + })); }); diff --git a/frontend/src/app/services/user.service.ts b/frontend/src/app/services/user.service.ts index 8900999470..aa0377888a 100644 --- a/frontend/src/app/services/user.service.ts +++ b/frontend/src/app/services/user.service.ts @@ -3,6 +3,7 @@ import { BehaviorSubject, Observable, of, tap } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { User } from '../shared/types/model/User'; import { NewUser } from '../shared/types/model/NewUser'; +import { UserOkrData } from '../shared/types/model/UserOkrData'; @Injectable({ providedIn: 'root', @@ -62,4 +63,16 @@ export class UserService { createUsers(userList: NewUser[]) { return this.httpClient.post(`${this.API_URL}/createall`, userList).pipe(tap(() => this.reloadUsers())); } + + isUserMemberOfTeams(user: User): Observable { + return this.httpClient.get(`${this.API_URL}/${user.id}/ismemberofteams`, {}); + } + + getUserOkrData(user: User): Observable { + return this.httpClient.get(`${this.API_URL}/${user.id}/userokrdata`, {}); + } + + deleteUser(user: User) { + return this.httpClient.delete(`${this.API_URL}/${user.id}`, {}).pipe(tap(() => this.reloadUsers())); + } } diff --git a/frontend/src/app/shared/dialog/alert-dialog/alert-dialog.component.html b/frontend/src/app/shared/dialog/alert-dialog/alert-dialog.component.html new file mode 100644 index 0000000000..c28fc1815d --- /dev/null +++ b/frontend/src/app/shared/dialog/alert-dialog/alert-dialog.component.html @@ -0,0 +1,29 @@ +
+ +
+ + + @if (data.dialogText) { +
+ {{ this.data.dialogText }} +
+ } + + @if (data.dialogDetails) { +
    + @for (item of this.data.dialogDetails; track $index) { +
    +
  • + {{ item }} +
  • +
    + } +
+ } +
+ + + + diff --git a/frontend/src/app/shared/dialog/alert-dialog/alert-dialog.component.scss b/frontend/src/app/shared/dialog/alert-dialog/alert-dialog.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/app/shared/dialog/alert-dialog/alert-dialog.component.spec.ts b/frontend/src/app/shared/dialog/alert-dialog/alert-dialog.component.spec.ts new file mode 100644 index 0000000000..696950c501 --- /dev/null +++ b/frontend/src/app/shared/dialog/alert-dialog/alert-dialog.component.spec.ts @@ -0,0 +1,48 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { MatButtonHarness } from '@angular/material/button/testing'; +import { AlertDialogComponent } from './alert-dialog.component'; + +const dialogMock = { + close: jest.fn(), +}; + +describe('AlertDialogComponent', () => { + let component: AlertDialogComponent; + let fixture: ComponentFixture; + let loader: HarnessLoader; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [AlertDialogComponent], + imports: [], + providers: [ + { provide: MAT_DIALOG_DATA, useValue: {} }, + { provide: MatDialogRef, useValue: dialogMock }, + ], + }); + fixture = TestBed.createComponent(AlertDialogComponent); + component = fixture.componentInstance; + loader = TestbedHarnessEnvironment.loader(fixture); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should have only one button', async () => { + let buttons = await loader.getAllHarnesses(MatButtonHarness); + expect(buttons.length).toBe(1); + }); + + it('should call close method with no parameter', async () => { + let buttons = await loader.getAllHarnesses(MatButtonHarness); + const submitButton = buttons[0]; + await submitButton.click(); + + expect(dialogMock.close).toHaveBeenCalledWith(); + }); +}); diff --git a/frontend/src/app/shared/dialog/alert-dialog/alert-dialog.component.ts b/frontend/src/app/shared/dialog/alert-dialog/alert-dialog.component.ts new file mode 100644 index 0000000000..d6081c0a02 --- /dev/null +++ b/frontend/src/app/shared/dialog/alert-dialog/alert-dialog.component.ts @@ -0,0 +1,24 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; + +export type AlertDialogData = { + dialogTitle: string; + dialogText?: string; + dialogDetails?: string[]; +}; + +@Component({ + selector: 'app-alert-dialog', + templateUrl: './alert-dialog.component.html', + styleUrl: './alert-dialog.component.scss', +}) +export class AlertDialogComponent { + constructor( + @Inject(MAT_DIALOG_DATA) public data: AlertDialogData, + private dialogRef: MatDialogRef, + ) {} + + closeDialog() { + this.dialogRef.close(); + } +} diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index 44a1110da3..b5e510f8a9 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -26,6 +26,7 @@ import { RouterOutlet } from '@angular/router'; import { SpinnerComponent } from './custom/spinner/spinner.component'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { CancelDialogComponent } from './dialog/cancel-dialog/cancel-dialog.component'; +import { AlertDialogComponent } from './dialog/alert-dialog/alert-dialog.component'; @NgModule({ declarations: [ @@ -42,6 +43,7 @@ import { CancelDialogComponent } from './dialog/cancel-dialog/cancel-dialog.comp SidepanelComponent, SpinnerComponent, CancelDialogComponent, + AlertDialogComponent, ], imports: [ CommonModule, diff --git a/frontend/src/app/shared/testData.ts b/frontend/src/app/shared/testData.ts index 48ba98a0de..f143b35e91 100644 --- a/frontend/src/app/shared/testData.ts +++ b/frontend/src/app/shared/testData.ts @@ -370,6 +370,21 @@ export const testUser: User = { email: 'bob.baumeister@puzzle.ch', }; +export const testOkrChampionUser: User = { + id: 1, + firstname: 'Hans', + lastname: 'Muster', + isOkrChampion: true, + userTeamList: [ + { + id: 1, + team: team1, + isTeamAdmin: false, + }, + ], + email: 'bob.baumeister@puzzle.ch', +}; + export const users: User[] = [ testUser, { diff --git a/frontend/src/app/shared/types/model/UserOkrData.ts b/frontend/src/app/shared/types/model/UserOkrData.ts new file mode 100644 index 0000000000..54e81cafd5 --- /dev/null +++ b/frontend/src/app/shared/types/model/UserOkrData.ts @@ -0,0 +1,10 @@ +export interface UserOkrData { + keyResults: UserKeyResultData[]; +} + +export interface UserKeyResultData { + keyResultId: number; + keyResultName: string; + objectiveId: number; + objectiveName: string; +} diff --git a/frontend/src/app/team-management/delete-user/delete-user.component.html b/frontend/src/app/team-management/delete-user/delete-user.component.html new file mode 100644 index 0000000000..8bebca1df1 --- /dev/null +++ b/frontend/src/app/team-management/delete-user/delete-user.component.html @@ -0,0 +1,14 @@ + + + diff --git a/frontend/src/app/team-management/delete-user/delete-user.component.scss b/frontend/src/app/team-management/delete-user/delete-user.component.scss new file mode 100644 index 0000000000..83cc63aea2 --- /dev/null +++ b/frontend/src/app/team-management/delete-user/delete-user.component.scss @@ -0,0 +1,9 @@ +.new-team { + display: flex; + + > mat-form-field { + flex: 0 0 calc(35% + 1rem); + padding-right: 1rem; + box-sizing: border-box; + } +} diff --git a/frontend/src/app/team-management/delete-user/delete-user.component.spec.ts b/frontend/src/app/team-management/delete-user/delete-user.component.spec.ts new file mode 100644 index 0000000000..954be4d5e8 --- /dev/null +++ b/frontend/src/app/team-management/delete-user/delete-user.component.spec.ts @@ -0,0 +1,185 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { UserService } from '../../services/user.service'; +import { DeleteUserComponent } from './delete-user.component'; +import { of } from 'rxjs'; +import { UserOkrData } from '../../shared/types/model/UserOkrData'; + +describe('DeleteUserComponent', () => { + let component: DeleteUserComponent; + let fixture: ComponentFixture; + + const userServiceMock = { + deleteUser: jest.fn(), + isUserMemberOfTeams: jest.fn(), + getUserOkrData: jest.fn(), + }; + + const matDialogMock = { + close: jest.fn(), + open: jest.fn(), + }; + + const matDialogRefMock = { + afterClosed: jest.fn(), + }; + + beforeEach(async () => { + jest.resetAllMocks(); + await TestBed.configureTestingModule({ + declarations: [], + imports: [], + providers: [{ provide: UserService, useValue: userServiceMock }], + }).compileComponents(); + + fixture = TestBed.createComponent(DeleteUserComponent); + component = fixture.componentInstance; + }); + + it('should create component', () => { + expect(component).toBeTruthy(); + }); + + it('should load UserOkrData and MemberTeamStatus (case is member of teams + with KeyResults)', () => { + const userOkrData: UserOkrData = { + keyResults: [ + { + keyResultId: 100, + keyResultName: 'keyResult1', + objectiveId: 200, + objectiveName: 'objective1', + }, + ], + }; + + component.currentTeams$ = of([]); + userServiceMock.getUserOkrData.mockReturnValue(of(userOkrData)); + userServiceMock.isUserMemberOfTeams.mockReturnValue(of(true)); + + component.ngOnInit(); + + expect(component.userOkrData).toBe(userOkrData); + expect(component.userIsMemberOfTeams).toBe(true); + }); + + it('should load UserOkrData and MemberTeamStatus (case not member of teams + no KeyResults)', () => { + const userOkrDataWithoutKeyResults: UserOkrData = { + keyResults: [], + }; + + component.currentTeams$ = of([]); + userServiceMock.getUserOkrData.mockReturnValue(of(userOkrDataWithoutKeyResults)); + userServiceMock.isUserMemberOfTeams.mockReturnValue(of(false)); + + component.ngOnInit(); + + expect(component.userOkrData).toBe(userOkrDataWithoutKeyResults); + expect(component.userIsMemberOfTeams).toBe(false); + }); + + it('should return true if userIsMemberOfTeams is undefined', () => { + component.userIsMemberOfTeams = undefined; + expect(component.isUserMemberOfTeams()).toBe(true); + }); + + it('should return true if userIsMemberOfTeams is true', () => { + component.userIsMemberOfTeams = true; + expect(component.isUserMemberOfTeams()).toBe(true); + }); + + it('should return false if userIsMemberOfTeams is false', () => { + component.userIsMemberOfTeams = false; + expect(component.isUserMemberOfTeams()).toBe(false); + }); + + it('should return true if userOkrData is undefined', () => { + component.userOkrData = undefined; + expect(component.isUserOwnerOfKeyResults()).toBe(true); + }); + + it('should return false if userOkrData has no keyResults', () => { + component.userOkrData = { + keyResults: [], + }; + + expect(component.isUserOwnerOfKeyResults()).toBe(false); + }); + + it('should return true if user is owner of keyResults', () => { + component.userOkrData = { + keyResults: [ + { + keyResultId: 100, + keyResultName: 'keyResult1', + objectiveId: 200, + objectiveName: 'objective1', + }, + ], + }; + + expect(component.isUserOwnerOfKeyResults()).toBe(true); + }); + + it('should not delete user when user is member of teams', () => { + component.user = { + id: 2, + firstname: 'Hans', + lastname: 'Muster', + isOkrChampion: false, + userTeamList: [], + email: 'hans.muster@puzzle.ch', + }; + component.userIsMemberOfTeams = true; + + component.deleteUser(); + + expect(userServiceMock.deleteUser).not.toHaveBeenCalledWith(component.user); + }); + + it('should not delete user when user is owner of keyResults', () => { + component.user = { + id: 2, + firstname: 'Hans', + lastname: 'Muster', + isOkrChampion: false, + userTeamList: [], + email: 'hans.muster@puzzle.ch', + }; + component.userIsMemberOfTeams = false; + component.userOkrData = { + keyResults: [ + { + keyResultId: 100, + keyResultName: 'keyResult1', + objectiveId: 200, + objectiveName: 'objective1', + }, + ], + }; + + component.deleteUser(); + + expect(userServiceMock.deleteUser).not.toHaveBeenCalledWith(component.user); + }); + + it('should delete user if user is not in a team and user has no keyResults', () => { + component.user = { + id: 2, + firstname: 'Hans', + lastname: 'Muster', + isOkrChampion: false, + userTeamList: [], + email: 'hans.muster@puzzle.ch', + }; + component.userOkrData = { + keyResults: [], + }; + component.userIsMemberOfTeams = false; + matDialogMock.open.mockReturnValue(matDialogRefMock); + matDialogRefMock.afterClosed.mockReturnValue(of()); + userServiceMock.deleteUser.mockReturnValue(of()); + + component.deleteUser(); + + setTimeout(() => expect(userServiceMock.deleteUser).toHaveBeenCalledWith(component.user), 100); + }); +}); diff --git a/frontend/src/app/team-management/delete-user/delete-user.component.ts b/frontend/src/app/team-management/delete-user/delete-user.component.ts new file mode 100644 index 0000000000..dc8f588153 --- /dev/null +++ b/frontend/src/app/team-management/delete-user/delete-user.component.ts @@ -0,0 +1,137 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { UserService } from '../../services/user.service'; +import { getFullNameFromUser, User } from '../../shared/types/model/User'; +import { Location } from '@angular/common'; +import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; +import { CancelDialogComponent, CancelDialogData } from '../../shared/dialog/cancel-dialog/cancel-dialog.component'; +import { OKR_DIALOG_CONFIG } from '../../shared/constantLibary'; +import { mergeMap, Observable } from 'rxjs'; +import { AlertDialogComponent, AlertDialogData } from '../../shared/dialog/alert-dialog/alert-dialog.component'; +import { UserOkrData } from '../../shared/types/model/UserOkrData'; +import { UserTeam } from '../../shared/types/model/UserTeam'; + +@Component({ + selector: 'app-delete-user', + templateUrl: './delete-user.component.html', + styleUrl: './delete-user.component.scss', +}) +export class DeleteUserComponent implements OnInit { + @Input({ required: true }) user!: User; + @Input({ required: true }) currentTeams$!: Observable; + + userIsMemberOfTeams: Boolean | undefined; + userOkrData: UserOkrData | undefined; + + constructor( + private readonly userService: UserService, + private readonly location: Location, + private readonly dialog: MatDialog, + ) {} + + ngOnInit() { + this.currentTeams$.subscribe(() => { + this.updateUserMemberTeamsStatus(); + }); + + this.loadUserOkrData(); + } + + updateUserMemberTeamsStatus() { + this.userService + .isUserMemberOfTeams(this.user) // + .subscribe((isMemberOfTeams) => (this.userIsMemberOfTeams = isMemberOfTeams)); + } + + loadUserOkrData() { + this.userService // + .getUserOkrData(this.user) // + .subscribe((okrData) => (this.userOkrData = okrData)); + } + + isUserMemberOfTeams(): boolean { + return this.userIsMemberOfTeams !== undefined ? this.userIsMemberOfTeams.valueOf() : true; + } + + isUserOwnerOfKeyResults(): boolean { + return this.userOkrData !== undefined ? this.userOkrData.keyResults.length > 0 : true; + } + + deleteUser() { + if (this.isUserMemberOfTeams()) { + this.showUnableToDeleteDialog( + this.dialogTitle(), // + this.dialogTextUserIsInTeams(), // + this.dialogDetailsUserTeams(8), + ); + return; + } + if (this.isUserOwnerOfKeyResults()) { + this.showUnableToDeleteDialog( + this.dialogTitle(), + this.dialogTextUserIsOwnerOfKeyResults(), + this.dialogDetailsUserKeyResults(8), + ); + return; + } + this.showDeleteUserDialog(this.user); + } + + private dialogTitle() { + return `${this.userInfo()} kann nicht gelöscht werden`; + } + + private userInfo() { + return getFullNameFromUser(this.user); + } + + private dialogTextUserIsInTeams() { + return `${this.userInfo()} ist in folgenden Teams und kann daher nicht gelöscht werden:`; + } + + private dialogTextUserIsOwnerOfKeyResults() { + return `${this.userInfo()} ist Owner folgender KeyResults und kann daher nicht gelöscht werden:`; + } + + private dialogDetailsUserTeams(showMaxTeams: number) { + if (this.userOkrData) { + return this.user.userTeamList // + .filter((_, index) => index < showMaxTeams) // + .map((userTeam) => userTeam.team.name); + } + return []; + } + + private dialogDetailsUserKeyResults(showMaxKeyResults: number) { + if (this.userOkrData) { + return this.userOkrData.keyResults + .filter((_, index) => index < showMaxKeyResults) + .map((data) => data.keyResultName + ' (Objective: ' + data.objectiveName + ')'); + } + return []; + } + + showUnableToDeleteDialog(title: string, text: string, details: string[]) { + const dialogConfig: MatDialogConfig = OKR_DIALOG_CONFIG; + dialogConfig.data = { + dialogTitle: title, + dialogText: text, + dialogDetails: details, + }; + this.dialog.open(AlertDialogComponent, dialogConfig); + } + + showDeleteUserDialog(user: User) { + const dialogConfig: MatDialogConfig = OKR_DIALOG_CONFIG; + dialogConfig.data = { + dialogTitle: `Member ${getFullNameFromUser(user)} wirklich löschen?`, + }; + this.dialog + .open(CancelDialogComponent, dialogConfig) + .afterClosed() + .pipe(mergeMap(() => this.userService.deleteUser(user))) + .subscribe(() => { + this.userService.reloadUsers(); + this.location.back(); + }); + } +} diff --git a/frontend/src/app/team-management/member-detail/member-detail.component.html b/frontend/src/app/team-management/member-detail/member-detail.component.html index ea3baeeb00..75d0edb773 100644 --- a/frontend/src/app/team-management/member-detail/member-detail.component.html +++ b/frontend/src/app/team-management/member-detail/member-detail.component.html @@ -53,6 +53,9 @@

[currentTeams$]="currentUserTeams$.asObservable()" (addUserTeam)="addTeamMembership($event, userRef)" > +
+ +
diff --git a/frontend/src/app/team-management/member-detail/member-detail.component.spec.ts b/frontend/src/app/team-management/member-detail/member-detail.component.spec.ts index d7d7dc1fca..e069084f08 100644 --- a/frontend/src/app/team-management/member-detail/member-detail.component.spec.ts +++ b/frontend/src/app/team-management/member-detail/member-detail.component.spec.ts @@ -7,7 +7,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { BrowserModule } from '@angular/platform-browser'; import { SharedModule } from '../../shared/shared.module'; import { UserService } from '../../services/user.service'; -import { testUser } from '../../shared/testData'; +import { testOkrChampionUser, testUser } from '../../shared/testData'; import { AddUserTeamComponent } from '../add-user-team/add-user-team.component'; import { MatTableModule } from '@angular/material/table'; import { MatIconModule } from '@angular/material/icon'; @@ -31,6 +31,7 @@ describe('MemberDetailComponent', () => { const userServiceMock = { getUserById: jest.fn(), + getOrInitCurrentUser: jest.fn(), getCurrentUser: jest.fn(), reloadUsers: jest.fn(), reloadCurrentUser: jest.fn(), @@ -79,6 +80,7 @@ describe('MemberDetailComponent', () => { component = fixture.componentInstance; userServiceMock.getUserById.mockReturnValue(of(testUser)); + userServiceMock.getOrInitCurrentUser.mockReturnValue(of(testOkrChampionUser)); userServiceMock.getCurrentUser.mockReturnValue(testUser); userServiceMock.reloadCurrentUser.mockReturnValue(of(testUser)); diff --git a/frontend/src/app/team-management/member-detail/member-detail.component.ts b/frontend/src/app/team-management/member-detail/member-detail.component.ts index e407144f63..33d422d64d 100644 --- a/frontend/src/app/team-management/member-detail/member-detail.component.ts +++ b/frontend/src/app/team-management/member-detail/member-detail.component.ts @@ -20,6 +20,7 @@ export class MemberDetailComponent implements OnInit, OnDestroy { @ViewChild(MatTable) table!: MatTable; user: User | undefined; + okrUser: User | undefined; teams: Team[] = []; currentUserTeams$ = new BehaviorSubject([]); selectedUserIsLoggedInUser: boolean = false; @@ -39,6 +40,16 @@ export class MemberDetailComponent implements OnInit, OnDestroy { private readonly dialogService: DialogService, ) {} ngOnInit(): void { + this.loadSelectedUser(); + this.loadOkrUser(); + } + + ngOnDestroy(): void { + this.unsubscribe$.next(); + this.unsubscribe$.complete(); + } + + private loadSelectedUser() { this.route.paramMap .pipe( takeUntil(this.unsubscribe$), @@ -50,9 +61,19 @@ export class MemberDetailComponent implements OnInit, OnDestroy { .subscribe(); } - ngOnDestroy(): void { - this.unsubscribe$.next(); - this.unsubscribe$.complete(); + private loadOkrUser() { + this.userService + .getOrInitCurrentUser() + .pipe( + takeUntil(this.unsubscribe$), + tap((user) => (this.okrUser = user)), + ) + .subscribe(); + } + + public hasOkrUserRoleOkrChampion() { + if (this.okrUser == undefined) return false; + return this.okrUser.isOkrChampion; } private loadUser(userId: number) { diff --git a/frontend/src/app/team-management/team-management.module.ts b/frontend/src/app/team-management/team-management.module.ts index 03a57af44e..594e96d669 100644 --- a/frontend/src/app/team-management/team-management.module.ts +++ b/frontend/src/app/team-management/team-management.module.ts @@ -27,6 +27,7 @@ import { TeamRoleDropdownComponent } from './team-role-dropdown/team-role-dropdo import { MatSelectModule } from '@angular/material/select'; import { TranslateModule } from '@ngx-translate/core'; import { AddUserTeamComponent } from './add-user-team/add-user-team.component'; +import { DeleteUserComponent } from './delete-user/delete-user.component'; import { A11yModule } from '@angular/cdk/a11y'; import { TeamManagementMobileFilterComponent } from './team-management-mobile-filter/team-management-mobile-filter.component'; import { MemberListTableComponent } from './member-list/member-list-table/member-list-table.component'; @@ -55,6 +56,7 @@ import { InviteUserDialogComponent } from './invite-user-dialog/invite-user-dial MemberDetailComponent, TeamRoleDropdownComponent, AddUserTeamComponent, + DeleteUserComponent, TeamManagementMobileFilterComponent, MemberListTableComponent, MemberListMobileComponent,