From 794dcd36f30e17e8d59514151272539f4637451e Mon Sep 17 00:00:00 2001 From: John Rassa Date: Wed, 28 Jun 2023 09:02:35 -0400 Subject: [PATCH] feat(teams): Add `hasTeamRole` and `hasSomeTeamRoles` directives * Directives modeled after the user roles directives. * Updated team components to use the directives. * Cleaned up unused in view team component --- .../has-some-team-roles.directive.ts | 76 +++++++++++++++++++ .../directives/has-team-role.directive.ts | 76 +++++++++++++++++++ .../list-team-members.component.html | 8 +- .../list-team-members.component.ts | 13 +--- .../core/teams/team-authorization.service.ts | 2 +- src/app/core/teams/teams.module.ts | 6 +- .../general-details.component.html | 2 +- .../general-details.component.ts | 12 +-- .../teams/view-team/view-team.component.html | 2 +- .../teams/view-team/view-team.component.ts | 54 +------------ 10 files changed, 172 insertions(+), 79 deletions(-) create mode 100644 src/app/core/teams/directives/has-some-team-roles.directive.ts create mode 100644 src/app/core/teams/directives/has-team-role.directive.ts diff --git a/src/app/core/teams/directives/has-some-team-roles.directive.ts b/src/app/core/teams/directives/has-some-team-roles.directive.ts new file mode 100644 index 00000000..e85b9b38 --- /dev/null +++ b/src/app/core/teams/directives/has-some-team-roles.directive.ts @@ -0,0 +1,76 @@ +import { NgIf } from '@angular/common'; +import { Directive, Input, OnInit, inject } from '@angular/core'; + +import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; + +import { AuthorizationService } from '../../auth/authorization.service'; +import { SessionService } from '../../auth/session.service'; +import { TeamAuthorizationService } from '../team-authorization.service'; +import { TeamRole } from '../team-role.model'; +import { Team } from '../team.model'; + +@UntilDestroy() +@Directive({ + selector: '[hasSomeTeamRoles]', + hostDirectives: [ + { + directive: NgIf, + inputs: ['ngIfElse: hasSomeTeamRolesElse', 'ngIfThen: hasSomeTeamRolesThen'] + } + ] +}) +export class HasSomeTeamRolesDirective implements OnInit { + private team: Pick; + private roles: Array = []; + private andCondition = true; + private orCondition = false; + + private ngIfDirective = inject(NgIf); + private sessionService = inject(SessionService); + private authorizationService = inject(AuthorizationService); + private teamAuthorizationService = inject(TeamAuthorizationService); + + @Input() + set hasSomeTeamRoles(team: Pick) { + this.team = team; + this.updateNgIf(); + } + + @Input() + set hasSomeTeamRolesRoles(roles: Array) { + this.roles = roles; + this.updateNgIf(); + } + + @Input() + set hasSomeTeamRolesAnd(condition: boolean) { + this.andCondition = condition; + this.updateNgIf(); + } + + @Input() + set hasSomeTeamRolesOr(condition: boolean) { + this.orCondition = condition; + this.updateNgIf(); + } + + ngOnInit() { + this.sessionService + .getSession() + .pipe(untilDestroyed(this)) + .subscribe(() => { + this.updateNgIf(); + }); + } + + protected checkPermission(): boolean { + return ( + this.authorizationService.isAdmin() || + this.teamAuthorizationService.hasSomeRoles(this.team, this.roles) + ); + } + + private updateNgIf() { + this.ngIfDirective.ngIf = this.orCondition || (this.andCondition && this.checkPermission()); + } +} diff --git a/src/app/core/teams/directives/has-team-role.directive.ts b/src/app/core/teams/directives/has-team-role.directive.ts new file mode 100644 index 00000000..420d26bf --- /dev/null +++ b/src/app/core/teams/directives/has-team-role.directive.ts @@ -0,0 +1,76 @@ +import { NgIf } from '@angular/common'; +import { Directive, Input, OnInit, inject } from '@angular/core'; + +import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; + +import { AuthorizationService } from '../../auth/authorization.service'; +import { SessionService } from '../../auth/session.service'; +import { TeamAuthorizationService } from '../team-authorization.service'; +import { TeamRole } from '../team-role.model'; +import { Team } from '../team.model'; + +@UntilDestroy() +@Directive({ + selector: '[hasTeamRole]', + hostDirectives: [ + { + directive: NgIf, + inputs: ['ngIfElse: hasTeamRoleElse', 'ngIfThen: hasTeamRoleThen'] + } + ] +}) +export class HasTeamRoleDirective implements OnInit { + private team: Pick; + private role: string | TeamRole; + private andCondition = true; + private orCondition = false; + + private ngIfDirective = inject(NgIf); + private sessionService = inject(SessionService); + private authorizationService = inject(AuthorizationService); + private teamAuthorizationService = inject(TeamAuthorizationService); + + @Input() + set hasTeamRole(team: Pick) { + this.team = team; + this.updateNgIf(); + } + + @Input() + set hasTeamRoleRole(role: string | TeamRole) { + this.role = role; + this.updateNgIf(); + } + + @Input() + set hasTeamRoleAnd(condition: boolean) { + this.andCondition = condition; + this.updateNgIf(); + } + + @Input() + set hasTeamRoleOr(condition: boolean) { + this.orCondition = condition; + this.updateNgIf(); + } + + ngOnInit() { + this.sessionService + .getSession() + .pipe(untilDestroyed(this)) + .subscribe(() => { + this.updateNgIf(); + }); + } + + protected checkPermission(): boolean { + return ( + this.authorizationService.isAdmin() || + this.teamAuthorizationService.hasRole(this.team, this.role) + ); + } + + private updateNgIf() { + this.ngIfDirective.ngIf = this.orCondition || (this.andCondition && this.checkPermission()); + } +} diff --git a/src/app/core/teams/list-team-members/list-team-members.component.html b/src/app/core/teams/list-team-members/list-team-members.component.html index 64d7ac6c..51ecde9e 100644 --- a/src/app/core/teams/list-team-members/list-team-members.component.html +++ b/src/app/core/teams/list-team-members/list-team-members.component.html @@ -11,10 +11,10 @@
@@ -71,7 +71,7 @@ class="dropdown dropdown-table-inline" dropdown container="body" - *ngIf="canManageTeam; else readOnlyRole" + *hasTeamRole="team; role: 'admin'; else: readOnlyRole" > {{ member.roleDisplay }} @@ -95,12 +95,12 @@ diff --git a/src/app/core/teams/list-team-members/list-team-members.component.ts b/src/app/core/teams/list-team-members/list-team-members.component.ts index a766bb77..6ca82737 100644 --- a/src/app/core/teams/list-team-members/list-team-members.component.ts +++ b/src/app/core/teams/list-team-members/list-team-members.component.ts @@ -48,8 +48,6 @@ export class ListTeamMembersComponent implements OnChanges, OnDestroy, OnInit { isUserAdmin = false; - canManageTeam = false; - teamRoleOptions: any[] = TeamRole.ROLES; typeFilterOptions: ListFilterOption[] = [ @@ -97,9 +95,6 @@ export class ListTeamMembersComponent implements OnChanges, OnDestroy, OnInit { } this.alertService.clearAllAlerts(); - this.canManageTeam = - this.authorizationService.isAdmin() || this.teamAuthorizationService.isAdmin(this.team); - this.sessionService .getSession() .pipe(isNotNullOrUndefined(), untilDestroyed(this)) @@ -108,9 +103,9 @@ export class ListTeamMembersComponent implements OnChanges, OnDestroy, OnInit { this.isUserAdmin = this.authorizationService.isAdmin(); }); - this.displayedColumns = this.columns - .filter((column) => this.canManageTeam || column !== 'actions') - .filter((column) => this.team.implicitMembers || column !== 'explicit'); + this.displayedColumns = this.columns.filter( + (column) => this.team.implicitMembers || column !== 'explicit' + ); } ngOnChanges(changes: SimpleChanges) { @@ -210,7 +205,7 @@ export class ListTeamMembersComponent implements OnChanges, OnDestroy, OnInit { ) .subscribe(() => { // If we successfully removed the role from ourselves, redirect away - this.router.navigate(['/teams', { clearCachedFilter: true }]); + this.router.navigate(['/teams']); }); } else if (!member.explicit) { // Member is implicitly in team, should explicitly add this member with the desired role diff --git a/src/app/core/teams/team-authorization.service.ts b/src/app/core/teams/team-authorization.service.ts index bea55a55..8508070a 100644 --- a/src/app/core/teams/team-authorization.service.ts +++ b/src/app/core/teams/team-authorization.service.ts @@ -69,7 +69,7 @@ export class TeamAuthorizationService { private roleToString(role: string | TeamRole) { if (typeof role !== 'string') { - return role.role; + return role?.role; } return role; } diff --git a/src/app/core/teams/teams.module.ts b/src/app/core/teams/teams.module.ts index c16e405f..021d9701 100644 --- a/src/app/core/teams/teams.module.ts +++ b/src/app/core/teams/teams.module.ts @@ -17,6 +17,8 @@ import { SystemAlertModule } from '../../common/system-alert.module'; import { TableModule } from '../../common/table.module'; import { AddMembersModalComponent } from './add-members-modal/add-members-modal.component'; import { CreateTeamComponent } from './create-team/create-team.component'; +import { HasSomeTeamRolesDirective } from './directives/has-some-team-roles.directive'; +import { HasTeamRoleDirective } from './directives/has-team-role.directive'; import { TeamsHelpComponent } from './help/teams-help.component'; import { ListTeamMembersComponent } from './list-team-members/list-team-members.component'; import { ListSubTeamsComponent } from './list-teams/list-sub-teams.component'; @@ -57,7 +59,9 @@ import { ViewTeamComponent } from './view-team/view-team.component'; ViewTeamComponent, TeamsHelpComponent, TeamSelectInputComponent, - GeneralDetailsComponent + GeneralDetailsComponent, + HasTeamRoleDirective, + HasSomeTeamRolesDirective ], providers: [TeamsHelpComponent] }) diff --git a/src/app/core/teams/view-team/general-details/general-details.component.html b/src/app/core/teams/view-team/general-details/general-details.component.html index 6a99bcb3..95553bfb 100644 --- a/src/app/core/teams/view-team/general-details/general-details.component.html +++ b/src/app/core/teams/view-team/general-details/general-details.component.html @@ -18,8 +18,8 @@

Details

class="btn btn-outline-secondary ml-3" type="button" [disabled]="isEditing" - *ngIf="canManageTeam" (click)="edit()" + *hasTeamRole="team; role: 'admin'" > diff --git a/src/app/core/teams/view-team/general-details/general-details.component.ts b/src/app/core/teams/view-team/general-details/general-details.component.ts index a8a66a7d..ea9a3332 100644 --- a/src/app/core/teams/view-team/general-details/general-details.component.ts +++ b/src/app/core/teams/view-team/general-details/general-details.component.ts @@ -5,10 +5,8 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { first, map, switchMap, tap } from 'rxjs/operators'; import { SystemAlertService } from '../../../../common/system-alert/system-alert.service'; -import { AuthorizationService } from '../../../auth/authorization.service'; import { SessionService } from '../../../auth/session.service'; import { ConfigService } from '../../../config.service'; -import { TeamAuthorizationService } from '../../team-authorization.service'; import { Team } from '../../team.model'; import { TeamsService } from '../../teams.service'; @@ -25,8 +23,6 @@ export class GeneralDetailsComponent implements OnInit { nestedTeamsEnabled = false; implicitMembersStrategy?: string; - canManageTeam = false; - isEditing = false; constructor( @@ -35,9 +31,7 @@ export class GeneralDetailsComponent implements OnInit { private alertService: SystemAlertService, private configService: ConfigService, private sessionService: SessionService, - private teamsService: TeamsService, - private authorizationService: AuthorizationService, - private teamAuthorizationService: TeamAuthorizationService + private teamsService: TeamsService ) {} ngOnInit(): void { @@ -62,8 +56,6 @@ export class GeneralDetailsComponent implements OnInit { updateTeam(team: Team) { if (team) { this.team = team; - this.canManageTeam = - this.authorizationService.isAdmin() || this.teamAuthorizationService.isAdmin(team); } else { this.router.navigate(['resource/invalid', { type: 'team' }]); } @@ -86,7 +78,7 @@ export class GeneralDetailsComponent implements OnInit { this.isEditing = false; if (team) { this.team = team; - this.alertService.addAlert('Updated team metadata', 'success', 5000); + this.alertService.addAlert('Team updated', 'success', 5000); } }), switchMap(() => this.sessionService.reloadSession()), diff --git a/src/app/core/teams/view-team/view-team.component.html b/src/app/core/teams/view-team/view-team.component.html index 2b0dee27..d0cbdbe3 100644 --- a/src/app/core/teams/view-team/view-team.component.html +++ b/src/app/core/teams/view-team/view-team.component.html @@ -15,7 +15,7 @@

{{ team.name }}

type="button" class="btn btn-link ml-auto" (click)="remove(team)" - *ngIf="canManageTeam" + *hasTeamRole="team; role: 'admin'" > Delete Team diff --git a/src/app/core/teams/view-team/view-team.component.ts b/src/app/core/teams/view-team/view-team.component.ts index 1c1828bc..018bd00c 100644 --- a/src/app/core/teams/view-team/view-team.component.ts +++ b/src/app/core/teams/view-team/view-team.component.ts @@ -7,10 +7,7 @@ import { filter, first, map, switchMap, tap } from 'rxjs/operators'; import { ModalAction } from '../../../common/modal/modal.model'; import { ModalService } from '../../../common/modal/modal.service'; import { SystemAlertService } from '../../../common/system-alert/system-alert.service'; -import { AuthorizationService } from '../../auth/authorization.service'; import { SessionService } from '../../auth/session.service'; -import { ConfigService } from '../../config.service'; -import { TeamAuthorizationService } from '../team-authorization.service'; import { TeamTopic, TeamTopics } from '../team-topic.model'; import { Team } from '../team.model'; import { TeamsService } from '../teams.service'; @@ -24,38 +21,19 @@ import { TeamsService } from '../teams.service'; export class ViewTeamComponent implements OnInit { topics: TeamTopic[] = []; team?: Team; - _team: any; - - nestedTeamsEnabled = false; - implicitMembersStrategy?: string; - - canManageTeam = false; - - isEditing = false; constructor( private router: Router, private route: ActivatedRoute, private modalService: ModalService, - private configService: ConfigService, private teamsService: TeamsService, private alertService: SystemAlertService, - private authorizationService: AuthorizationService, - private sessionService: SessionService, - private teamAuthorizationService: TeamAuthorizationService + private sessionService: SessionService ) { this.topics = TeamTopics.getTopics(); } ngOnInit() { - this.configService - .getConfig() - .pipe(first(), untilDestroyed(this)) - .subscribe((config) => { - this.implicitMembersStrategy = config?.teams?.implicitMembers?.strategy; - this.nestedTeamsEnabled = config?.teams?.nestedTeams ?? false; - }); - this.route.data .pipe( map((data) => data['team']), @@ -66,37 +44,9 @@ export class ViewTeamComponent implements OnInit { }); } - edit() { - this._team = new Team().setFromModel(this.team); - this.isEditing = true; - } - - cancelEdit() { - this.isEditing = false; - } - - saveEdit() { - this.teamsService - .update(this._team) - .pipe( - tap((team: Team | null) => { - this.isEditing = false; - if (team) { - this.team = team; - this.alertService.addAlert('Updated team metadata', 'success', 5000); - } - }), - switchMap(() => this.sessionService.reloadSession()), - untilDestroyed(this) - ) - .subscribe(); - } - updateTeam(team: Team) { if (team) { this.team = team; - this.canManageTeam = - this.authorizationService.isAdmin() || this.teamAuthorizationService.isAdmin(team); } else { this.router.navigate(['resource/invalid', { type: 'team' }]); } @@ -116,6 +66,6 @@ export class ViewTeamComponent implements OnInit { switchMap(() => this.sessionService.reloadSession()), untilDestroyed(this) ) - .subscribe(() => this.router.navigate(['/teams', { clearCachedFilter: true }])); + .subscribe(() => this.router.navigate(['/teams'])); } }