private static async Task <long> GetLatestPullRequestIterationAsync(SubscriptionEvent <PullRequestEvent> pullRequestInfo, string accessToken) { var url = $"{pullRequestInfo.ResourceContainers.Account.BaseUrl}" + $"/_apis/git/repositories/{pullRequestInfo.Resource.Repository.Id}" + $"/pullRequests/{pullRequestInfo.Resource.PullRequestId}/iterations?api-version=4.1"; using (var httpClient = new HttpClient()) { httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var resp = await httpClient.GetStringAsync(url); var data = JsonConvert.DeserializeObject <PullRequestIterations>(resp); return(data .Value .Select(v => v.Id) .OrderByDescending(v => v) .FirstOrDefault()); } }
private static async Task <IEnumerable <AssociatedWorkItem> > GetPullRequestWorkItemsAsync( string accessToken, SubscriptionEvent <PullRequestEvent> pullRequestInfo, List <string> fieldsRequired) { fieldsRequired.Add("System.WorkItemType"); var url = pullRequestInfo.ResourceContainers.Account.BaseUrl + $"_apis/git/repositories/{pullRequestInfo.Resource.Repository.Id}" + $"/pullRequests/{pullRequestInfo.Resource.PullRequestId}/workitems?api-version=4.1"; using (var httpClient = new HttpClient()) { httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var resp = await httpClient.GetAsync(url).ConfigureAwait(false); if (!resp.IsSuccessStatusCode) { return(new List <AssociatedWorkItem>()); } var json = await resp.Content.ReadAsStringAsync(); var workItemIds = JsonConvert.DeserializeObject <Response <AssociatedWorkItem> >(json).Value.Select(v => v.Id); var detailUrl = pullRequestInfo.ResourceContainers.Account.BaseUrl + $"_apis/wit/workItems" + $"?ids={Uri.EscapeDataString(string.Join(",", workItemIds))}" + $"&fields={Uri.EscapeDataString(string.Join(",", fieldsRequired))}"; resp = await httpClient.GetAsync(detailUrl).ConfigureAwait(false); json = await resp.Content.ReadAsStringAsync(); if (!resp.IsSuccessStatusCode) { return(new List <AssociatedWorkItem>()); } return(JsonConvert.DeserializeObject <Response <AssociatedWorkItem> >(json).Value); } }
public static async Task UpdateStatusAsync( [QueueTrigger("update-pr-status")] SubscriptionEvent <PullRequestEvent> pullRequestInfo, [Table("Projects")] CloudTable projects, [Table("Rules")] CloudTable rulesTable) { var partitionKey = new Uri(pullRequestInfo.ResourceContainers.Account.BaseUrl).Authority; var rowKey = pullRequestInfo.ResourceContainers.Project.Id; var project = projects.CreateQuery <ProjectEntity>() .Where(p => p.PartitionKey == partitionKey && p.RowKey == rowKey) .ToList() .First(); await Helpers.UpdateAccessToken(projects, project.PartitionKey, project.RowKey).ConfigureAwait(false); var accessToken = project.AccessToken; var pr = pullRequestInfo.Resource; if (pr.Reviewers.Count == 0) { // don't set a status if there are no reviewers return; } var percentReviewed = (pr.Reviewers.Count(r => r.Vote != 0) / (float)pr.Reviewers.Count) * 100.0f; var vote = pr.Reviewers.Sum(r => r.Vote); var reviewed = percentReviewed >= 100; var statusUrl = pullRequestInfo.ResourceContainers.Account.BaseUrl + $"{pr.Repository.Project.Id}/_apis/git/repositories/{pr.Repository.Id}" + $"/pullRequests/{pr.PullRequestId}/statuses?api-version=4.1-preview"; var latestIteration = await GetLatestPullRequestIterationAsync(pullRequestInfo, accessToken); var description = $"Waiting for {pr.Reviewers.Count(r => r.Vote == 0)} reviewers"; var state = PullRequestState.Pending; if (reviewed) { if (vote > 0) { state = PullRequestState.Succeeded; description = "All reviews approved"; } else { state = PullRequestState.Error; description = "Reviews completed with rejections "; } } var update = new PullRequestStatusUpdate { State = state, Context = new Context { Name = "review-check", Genre = "vsts-pullrequest-bot" }, TargetUrl = pr.Links.Web.Href, Description = description, IterationId = latestIteration }; using (var httpClient = new HttpClient()) { httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var resp = await httpClient.PostAsJsonAsync(statusUrl, update).ConfigureAwait(false); resp.EnsureSuccessStatusCode(); } var rules = rulesTable.CreateQuery <RuleEntity>() .Where(r => r.PartitionKey == rowKey) .ToList() .GroupBy(r => r.RuleType); foreach (var ruleGroup in rules) { switch (ruleGroup.Key) { case RuleType.WorkItemTaskUpdate: var ruleUpdates = ruleGroup .Select(rg => new WorkItemUpdate { Op = "add", Path = rg.Key, Value = rg.Value }); foreach (var workItem in (await GetPullRequestWorkItemsAsync( accessToken, pullRequestInfo, ruleUpdates.Select(ru => ru.Path).ToList())) .Where(wi => wi.Fields["System.WorkItemType"] == "Task")) { ruleUpdates = ruleUpdates .Where(ru => workItem.Fields[ru.Path] != ru.Value) .Select(ru => new WorkItemUpdate { Op = ru.Op, Path = $"/fields/{ru.Path}", Value = ru.Value }); if (!ruleUpdates.Any()) { return; } var url = pullRequestInfo.ResourceContainers.Account.BaseUrl + $"_apis/wit/workitems/{workItem.Id}?api-version=4.1"; using (var httpClient = new HttpClient()) { httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var req = new HttpRequestMessage(new HttpMethod("PATCH"), url); req.Content = new StringContent(JsonConvert.SerializeObject(ruleUpdates)); req.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json-patch+json"); var resp = await httpClient.SendAsync(req).ConfigureAwait(false); var json = await resp.Content.ReadAsStringAsync(); } } break; } } }