Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Conflicts resolved
  • Loading branch information
Whitney Shake committed Jul 9, 2024
commit 5f0ffab92d6a26260ff80bf948798ee773bcccdc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
using System;
using APIViewWeb.Managers;
using Microsoft.Azure.Cosmos.Serialization.HybridRow;
using System.Collections.Generic;
using System.Linq;

namespace APIViewWeb.LeanControllers
{
Expand All @@ -17,15 +19,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 +116,33 @@ 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.GetAPIRevisionAsync(apiRevisionId);
var existingReviewers = apiRevision.AssignedReviewers;
var newReviewers = reviewers.Where(reviewer => !existingReviewers.Any(existingReviewer => existingReviewer.AssingedTo == reviewer)).ToHashSet();
var removedReviewers = existingReviewers.Where(existingReviewer => !reviewers.Contains(existingReviewer.AssingedTo)).Select(r => r.AssingedTo).ToHashSet();

if (newReviewers.Any())
{
await _apiRevisionsManager.AssignReviewersToAPIRevisionAsync(User, apiRevisionId, newReviewers);
await _notificationManager.NotifyApproversOfReview(User, apiRevisionId, newReviewers);
}
if (removedReviewers.Any())
{
await _apiRevisionsManager.RemoveReviewersFromAPIRevisionAsync(User, apiRevisionId, removedReviewers);
}
return new LeanJsonResult(apiRevision, StatusCodes.Status200OK);
}
}
}
20 changes: 20 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,26 @@ public async Task AssignReviewersToAPIRevisionAsync(ClaimsPrincipal User, string
await _apiRevisionsRepository.UpsertAPIRevisionAsync(apiRevision);
}

/// <summary>
/// Remove reviewers from a review
/// </summary>
/// <param name="User"></param>
/// <param name="apiRevisionId"></param>
/// <param name="reviewers"></param>
/// <returns></returns>
public async Task RemoveReviewersFromAPIRevisionAsync(ClaimsPrincipal User, string apiRevisionId, HashSet<string> reviewers)
{
APIRevisionListItemModel apiRevision = await _apiRevisionsRepository.GetAPIRevisionAsync(apiRevisionId);
var reviewersToRemove = apiRevision.AssignedReviewers.Where(x => reviewers.Contains(x.AssingedTo)).ToList();

foreach (var reviewAssignment in reviewersToRemove)
{
apiRevision.AssignedReviewers.Remove(reviewAssignment);
}

await _apiRevisionsRepository.UpsertAPIRevisionAsync(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 RemoveReviewersFromAPIRevisionAsync(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,18 @@
<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)="handleOnPanelHide()"
(onPanelShow)="handleOnPanelShow()">
<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 { CookieService } from 'ngx-cookie-service';
import { RevisionsService } from 'src/app/_services/revisions/revisions.service';

@Component({
selector: 'app-review-page-options',
Expand All @@ -20,10 +22,17 @@ export class ReviewPageOptionsComponent implements OnInit, OnChanges{
@Input() review : Review | undefined = undefined;
@Input() activeAPIRevision : APIRevision | undefined = undefined;
@Input() diffAPIRevision : APIRevision | undefined = undefined;
<<<<<<< HEAD
@Input() preferedApprovers: string[] = [];
=======
@Input() preferredApprovers: string[] = [];
@Input() conversiationInfo : any | undefined = undefined;
>>>>>>> 731fd433d (Select Reviewers functional and updating DB)
@Input() hasFatalDiagnostics : boolean = false;
@Input() hasActiveConversation : boolean = false;
@Input() hasHiddenAPIs : boolean = false;
@Input() reviewId: string | undefined;
@Input() apiRevisionId: string | undefined;

@Output() diffStyleEmitter : EventEmitter<string> = new EventEmitter<string>();
@Output() showCommentsEmitter : EventEmitter<boolean> = new EventEmitter<boolean>();
Expand Down Expand Up @@ -59,7 +68,9 @@ export class ReviewPageOptionsComponent implements OnInit, OnChanges{
reviewIsApproved: boolean | undefined = undefined;
reviewApprover: string = 'azure-sdk';

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

diffStyleOptions : any[] = [
{ label: 'Full Diff', value: "full" },
Expand All @@ -76,7 +87,12 @@ 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 cookieService: CookieService,
private apiRevisionsService: RevisionsService) { }

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

const selectedApproversCookie = this.cookieService.get('selectedApprovers');
if (selectedApproversCookie) {
this.selectedApprovers = JSON.parse(selectedApproversCookie);
}

this.setAPIRevisionApprovalStates();
this.setReviewApprovalStatus();
}
Expand Down Expand Up @@ -206,6 +227,36 @@ export class ReviewPageOptionsComponent implements OnInit, OnChanges{
this.showHiddenAPIEmitter.emit(event.checked);
}

handleOnPanelShow() {
this.initialSelectedApprovers = [...this.selectedApprovers];
}

handleOnPanelHide() {
if (!this.reviewId || !this.apiRevisionId) {
return;
}

const { isSelectedApproversChanged, currentApproversSet } = this.hasSelectedApproversChanged();

if (!isSelectedApproversChanged) {
return;
}
this.apiRevisionsService.updateSelectedReviewers(this.reviewId, this.apiRevisionId, currentApproversSet).subscribe();
this.cookieService.set('selectedApprovers', JSON.stringify(this.selectedApprovers));
}

hasSelectedApproversChanged() {
const currentApproversSet = new Set(this.selectedApprovers);
const initialApproversSet = new Set(this.initialSelectedApprovers);
const isSelectedApproversChanged = this.selectedApprovers.length !== this.initialSelectedApprovers.length ||
[...currentApproversSet].some(approver => !initialApproversSet.has(approver));
return { isSelectedApproversChanged, currentApproversSet };
}

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 @@ -51,6 +51,8 @@
[hasFatalDiagnostics]="hasFatalDiagnostics"
[hasActiveConversation]="hasActiveConversation"
[hasHiddenAPIs]="hasHiddenAPIs"
[reviewId]="reviewId ?? undefined"
[apiRevisionId]="activeApiRevisionId ?? undefined"
(showSystemCommentsEmitter)="handleShowSystemCommentsEmitter($event)"
(showDocumentationEmitter)="handleShowDocumentationEmitter($event)"
(showCommentsEmitter)="handleShowCommentsEmitter($event)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ export class ReviewPageComponent implements OnInit {
scrollToNodeIdHashed : string | undefined;
scrollToNodeId : string | undefined = undefined;
showLineNumbers : boolean = true;
preferedApprovers : string[] = [];
preferredApprovers : string[] = [];
conversiationInfo : any | undefined = undefined;
hasFatalDiagnostics : boolean = false;
hasActiveConversation : boolean = false;
hasHiddenAPIs : boolean = false;
Expand Down Expand Up @@ -97,7 +98,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 +188,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,
});
}
}