Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
using APIViewWeb.Managers.Interfaces;
using System;
using APIViewWeb.Managers;
using Microsoft.Azure.Cosmos.Serialization.HybridRow;
using System.Collections.Generic;

namespace APIViewWeb.LeanControllers
{
Expand All @@ -17,15 +16,18 @@ public class APIRevisionsController : BaseApiController
private readonly ILogger<APIRevisionsController> _logger;
private readonly IAPIRevisionsManager _apiRevisionsManager;
private readonly IReviewManager _reviewManager;
private readonly INotificationManager _notificationManager;


public APIRevisionsController(ILogger<APIRevisionsController> logger,
IReviewManager reviewManager,
IAPIRevisionsManager apiRevisionsManager)
IAPIRevisionsManager apiRevisionsManager,
INotificationManager notificationManager)
{
_logger = logger;
_apiRevisionsManager = apiRevisionsManager;
_reviewManager = reviewManager;
_notificationManager = notificationManager;
}

/// <summary>
Expand Down Expand Up @@ -111,5 +113,22 @@ public async Task<ActionResult<APIRevisionListItemModel>> ToggleReviewApprovalAs
}
return new LeanJsonResult(apiRevision, StatusCodes.Status200OK);
}

/// <summary>
/// Endpoint used by Client SPA for Requesting Reviewers
/// </summary>
/// <param name="reviewId"></param>
/// <param name="apiRevisionId"></param>
/// <param name="reviewers"></param>
/// <returns></returns>

[HttpPost("{reviewId}/{apiRevisionId}/reviewers", Name = "AddReviewers")]
public async Task<ActionResult<APIRevisionListItemModel>> AddReviewersAsync(string reviewId, string apiRevisionId, [FromBody] HashSet<string> reviewers)
{
var apiRevision = await _apiRevisionsManager.UpdateAPIRevisionReviewersAsync(User, apiRevisionId, reviewers);
await _notificationManager.NotifyApproversOfReview(User, apiRevisionId, reviewers);

return new LeanJsonResult(apiRevision, StatusCodes.Status200OK);
}
}
}
24 changes: 24 additions & 0 deletions src/dotnet/APIView/APIViewWeb/Managers/APIRevisionsManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,30 @@ public async Task AssignReviewersToAPIRevisionAsync(ClaimsPrincipal User, string
await _apiRevisionsRepository.UpsertAPIRevisionAsync(apiRevision);
}

public async Task<APIRevisionListItemModel> UpdateAPIRevisionReviewersAsync(ClaimsPrincipal User, string apiRevisionId, HashSet<string> reviewers)
{
APIRevisionListItemModel apiRevision = await _apiRevisionsRepository.GetAPIRevisionAsync(apiRevisionId);
foreach (var reviewer in reviewers)
{
if (!apiRevision.AssignedReviewers.Where(x => x.AssingedTo == reviewer).Any())
{
var reviewAssignment = new ReviewAssignmentModel()
{
AssingedTo = reviewer,
AssignedBy = User.GetGitHubLogin(),
AssingedOn = DateTime.Now,
};
apiRevision.AssignedReviewers.Add(reviewAssignment);
}
}
foreach (var assignment in apiRevision.AssignedReviewers.FindAll(x => !reviewers.Contains(x.AssingedTo)))
{
apiRevision.AssignedReviewers.Remove(assignment);
}
await _apiRevisionsRepository.UpsertAPIRevisionAsync(apiRevision);
return apiRevision;
}

/// <summary>
/// Get Reviews that have been assigned for review to a user
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ public Task<APIRevisionListItemModel> CreateAPIRevisionAsync(string userName, st
public Task<IEnumerable<APIRevisionListItemModel>> GetAPIRevisionsAssignedToUser(string userName);
public Task<APIRevisionListItemModel> UpdateRevisionMetadataAsync(APIRevisionListItemModel revision, string packageVersion, string label, bool setReleaseTag = false);
public Task<IEnumerable<string>> GetReviewIdsOfLanguageCorrespondingReviewAsync(string crossLanguagePackageId);
public Task<APIRevisionListItemModel> UpdateAPIRevisionReviewersAsync(ClaimsPrincipal User, string apiRevisionId, HashSet<string> reviewers);
}
}
3 changes: 3 additions & 0 deletions src/dotnet/APIView/ClientSPA/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,5 +123,8 @@
}
}
}
},
"cli": {
"analytics": false
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<app-page-options-section sectionName="Approval">
<ul class="list-group">
<li class="list-group-item text-center">
<ng-container *ngIf="userProfile && preferedApprovers.includes(userProfile?.userName!)">
<ng-container *ngIf="userProfile && preferredApprovers.includes(userProfile?.userName!)">
<ng-container *ngIf="canToggleApproveAPIRevision; else cantToggleApproveAPIRevision">
<span *ngIf="apiRevisionApprovalMessage" class="small text-muted">{{apiRevisionApprovalMessage}}</span>
<div class="d-grid gap-2">
Expand Down Expand Up @@ -103,16 +103,17 @@
<li class="list-group-item">
<label class="small mx-1 fw-semibold" for="diff-style-select">Reviewers :</label>
<p-multiSelect
[options]="preferedApprovers"
[options]="preferredApprovers"
[(ngModel)]="selectedApprovers"
placeholder="Request Reviewers"
[showClear]="true"
[style]="{'width':'100%'}">
<ng-template let-approver pTemplate="selectedItems">
<div *ngIf="approver && approver.length > 0" class="inline-flex align-items-center gap-2 px-1 me-1">
<span>{{ approver }},</span>
[style]="{'width':'100%'}"
(onPanelHide)="handleAssignedReviewersChange()">
<ng-template pTemplate="selectedItems">
<div *ngIf="selectedApprovers && selectedApprovers.length > 0" class="inline-flex align-items-center gap-2 px-1 me-1">
<span>{{ formatSelectedApprovers(selectedApprovers) }}</span>
</div>
<div *ngIf="!approver || approver.length === 0">Select Reviewers</div>
<div *ngIf="!selectedApprovers || selectedApprovers.length === 0">Select Reviewers</div>
</ng-template>
<ng-template let-approver pTemplate="item">
<div class="flex align-items-center gap-2">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { UserProfile } from 'src/app/_models/auth_service_models';
import { Review } from 'src/app/_models/review';
import { APIRevision } from 'src/app/_models/revision';
import { ConfigService } from 'src/app/_services/config/config.service';
import { RevisionsService } from 'src/app/_services/revisions/revisions.service';
import { pipe, take } from 'rxjs';

@Component({
selector: 'app-review-page-options',
Expand All @@ -20,7 +22,7 @@ export class ReviewPageOptionsComponent implements OnInit, OnChanges{
@Input() review : Review | undefined = undefined;
@Input() activeAPIRevision : APIRevision | undefined = undefined;
@Input() diffAPIRevision : APIRevision | undefined = undefined;
@Input() preferedApprovers: string[] = [];
@Input() preferredApprovers: string[] = [];
@Input() hasFatalDiagnostics : boolean = false;
@Input() hasActiveConversation : boolean = false;
@Input() hasHiddenAPIs : boolean = false;
Expand Down Expand Up @@ -59,6 +61,7 @@ export class ReviewPageOptionsComponent implements OnInit, OnChanges{
reviewIsApproved: boolean | undefined = undefined;
reviewApprover: string = 'azure-sdk';

//Approvers Options
selectedApprovers: string[] = [];

diffStyleOptions : any[] = [
Expand All @@ -76,7 +79,11 @@ export class ReviewPageOptionsComponent implements OnInit, OnChanges{
'unDeleted': 'bi bi-plus-circle-fill undeleted'
};

constructor(private configService: ConfigService, private route: ActivatedRoute, private router: Router) { }
constructor(
private configService: ConfigService,
private route: ActivatedRoute,
private router: Router,
private apiRevisionsService: RevisionsService) { }

ngOnInit() {
this.setSelectedDiffStyle();
Expand All @@ -96,6 +103,7 @@ export class ReviewPageOptionsComponent implements OnInit, OnChanges{
this.showLineNumbersSwitch = true;
}

this.activeAPIRevision?.assignedReviewers.map(revision => this.selectedApprovers.push(revision.assingedTo));
this.setAPIRevisionApprovalStates();
this.setReviewApprovalStatus();
}
Expand Down Expand Up @@ -206,6 +214,27 @@ export class ReviewPageOptionsComponent implements OnInit, OnChanges{
this.showHiddenAPIEmitter.emit(event.checked);
}


handleAssignedReviewersChange() {

const existingApprovers = new Set(this.activeAPIRevision!.assignedReviewers.map(reviewer => reviewer.assingedTo));
const currentApprovers = new Set(this.selectedApprovers);
const isSelectedApproversChanged = existingApprovers.size !== currentApprovers.size ||
[...existingApprovers].some(approver => !currentApprovers.has(approver));

if (isSelectedApproversChanged) {
this.apiRevisionsService.updateSelectedReviewers(this.activeAPIRevision!.reviewId, this.activeAPIRevision!.id, currentApprovers).pipe(take(1)).subscribe({
next: (response: APIRevision) => {
this.activeAPIRevision = response;
}
});
}
}

formatSelectedApprovers(approvers: string[]): string {
return approvers.join(', ');
}

setSelectedDiffStyle() {
const inputDiffStyle = this.diffStyleOptions.find(option => option.value === this.diffStyleInput);
this.selectedDiffStyle = (inputDiffStyle) ? inputDiffStyle : this.diffStyleOptions[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
[review]="review"
[activeAPIRevision]="activeAPIRevision"
[diffAPIRevision]="diffAPIRevision"
[preferedApprovers]="preferedApprovers"
[preferredApprovers]="preferredApprovers"
[hasFatalDiagnostics]="hasFatalDiagnostics"
[hasActiveConversation]="hasActiveConversation"
[hasHiddenAPIs]="hasHiddenAPIs"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class ReviewPageComponent implements OnInit {
scrollToNodeIdHashed : string | undefined;
scrollToNodeId : string | undefined = undefined;
showLineNumbers : boolean = true;
preferedApprovers : string[] = [];
preferredApprovers : string[] = [];
hasFatalDiagnostics : boolean = false;
hasActiveConversation : boolean = false;
hasHiddenAPIs : boolean = false;
Expand Down Expand Up @@ -97,7 +97,7 @@ export class ReviewPageComponent implements OnInit {
this.reviewId = this.route.snapshot.paramMap.get(REVIEW_ID_ROUTE_PARAM);

this.loadReview(this.reviewId!);
this.loadPreferedApprovers(this.reviewId!);
this.loadPreferredApprovers(this.reviewId!);
this.loadAPIRevisions(0, this.apiRevisionPageSize);

this.sideMenu = [
Expand Down Expand Up @@ -187,11 +187,11 @@ export class ReviewPageComponent implements OnInit {
});
}

loadPreferedApprovers(reviewId: string) {
this.reviewsService.getPreferedApprovers(reviewId)
loadPreferredApprovers(reviewId: string) {
this.reviewsService.getPreferredApprovers(reviewId)
.pipe(takeUntil(this.destroy$)).subscribe({
next: (preferedApprovers: string[]) => {
this.preferedApprovers = preferedApprovers;
next: (preferredApprovers: string[]) => {
this.preferredApprovers = preferredApprovers;
}
});
}
Expand Down
1 change: 0 additions & 1 deletion src/dotnet/APIView/ClientSPA/src/app/_models/revision.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ export interface APIRevision {
viewedBy: string[]
}


export interface AssignedReviewer {
assignedBy: string;
assingedTo: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export class ReviewsService {
return this.http.get<Review>(this.baseUrl + `/${reviewId}`, { withCredentials: true });
}

getPreferedApprovers(reviewId: string) : Observable<string[]> {
getPreferredApprovers(reviewId: string) : Observable<string[]> {
return this.http.get<string[]>(this.baseUrl + `/${reviewId}/preferredApprovers`, { withCredentials: true });
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,17 @@ export class RevisionsService {
openAPIRevisionPage(reviewId: string, activeAPIRevisionId: string) {
window.open(this.configService.webAppUrl + `Assemblies/Review/${reviewId}?revisionId=${activeAPIRevisionId}`, '_blank');
}

updateSelectedReviewers(reviewId: string, apiRevisionId: string, reviewers: Set<string>): Observable<APIRevision> {
const headers = new HttpHeaders({
'Content-Type': 'application/json',
});

const reviewersArray = Array.from(reviewers);

return this.http.post<APIRevision>(`${this.baseUrl}/${reviewId}/${apiRevisionId}/reviewers`, reviewersArray, {
headers: headers,
withCredentials: true,
});
}
}