private async Task <IEnumerable <int> > FetchRelatedWorkItems(TeamMember member, IEnumerable <VSTSProject> projects) { var date = member.LastFetchDate == DateTime.MinValue ? DateTime.UtcNow.AddYears(-10) : member.LastFetchDate; var queryText = string.Format(WorkItemsQuery, member.Email, date.ToString("MM/dd/yyyy")); var query = new ItemsQuery(queryText); var ids = new List <int>(1000); foreach (var project in projects) { _logger.LogInformation("Processing workitems for project '{0}'", project.Name); var wiqlEndPoint = VSTSApiUrl.Create(_config.Value.InstanceName) .ForWIQL(project.Name) .Build(); var response = await _client.ExecutePost <WorkItemsQueryResponse>(wiqlEndPoint, query); ids.AddRange(response.WorkItems.Select(r => r.Id)); } if (member.RelatedWorkItemIds != null) { member.RelatedWorkItemIds = member.RelatedWorkItemIds.Union(ids).Distinct(); } else { member.RelatedWorkItemIds = ids; } return(ids); }
private async Task FetchUpdatesAndSave(IEnumerable <VSTSWorkItem> workItems) { _logger.LogInformation("Starting to fetch updates"); foreach (var wi in workItems) { try { var url = VSTSApiUrl.Create(_config.Value.InstanceName) .ForWorkItems(wi.WorkItemId) .WithSection("updates") .Build(); var updates = await _client.ExecuteGet <ValueResponse <WorkItemUpdate> >(url); wi.Updates = updates.Value.Where(u => !u.CanBeDiscarded); await _repository.CreateOrUpdateAsync(wi, i => i.WorkItemId == wi.WorkItemId); } catch (Exception ex) { _logger.LogWarning(ex, "Error updating workitem {WorkItemId}", wi.WorkItemId); // TODO: Skipping ivalid workitems for now. Consider filtering updates to only include relevant fields. } } _logger.LogInformation("Finished fetching updates"); }
private async Task <IEnumerable <VSTSWorkItem> > GetWorkItems(IEnumerable <int> changedWorkItems) { var count = (decimal)changedWorkItems.Count(); const int maxIdsPerRequest = 200; const string fieldsString = "System.Id,System.WorkItemType,System.Title,System.AreaPath,System.ChangedDate,System.Tags,System.State,System.Reason," + "System.CreatedDate,Microsoft.VSTS.Common.ResolvedDate,Microsoft.VSTS.Common.ClosedDate,Microsoft.VSTS.Common.StateChangeDate," + "Microsoft.VSTS.Scheduling.OriginalEstimate,Microsoft.VSTS.Scheduling.CompletedWork,Microsoft.VSTS.Scheduling.RemainingWork"; var iterations = Math.Ceiling(count / maxIdsPerRequest); var wis = new List <VSTSWorkItem>((int)count); _logger.LogInformation("Starting workitems fetch. Total count: {0}, iterations needed: {1}", count, iterations); for (int i = 0; i < iterations; i++) { _logger.LogInformation("Starting iteration {0}", i); var idsToQuery = string.Join(',', changedWorkItems.Skip(i * maxIdsPerRequest).Take(maxIdsPerRequest)); var wiQuery = VSTSApiUrl.Create(_config.Value.InstanceName) .ForWorkItemsBatch(idsToQuery) .WithQueryParameter("fields", fieldsString) .Build(); var workItemsResponse = await _client.ExecuteGet <ValueResponse <VSTSWorkItem> >(wiQuery); wis.AddRange(workItemsResponse.Value); } return(wis); }
public async Task <IActionResult> OnPost() { if (!ModelState.IsValid) { return(Page()); } var identityUrl = VSTSApiUrl.Create(_config.Value.InstanceName, "vssps") .ForIdentities() .WithQueryParameter("searchFilter", "General") .WithQueryParameter("filterValue", Member.Email) .Build("4.0"); var identities = await _client.ExecuteGet <ValueBasedResponse <IdentityResponse> >(identityUrl); var users = identities.Value .Where(u => u.IsActive) .ToList(); if (users.Count == 1) { var member = new TeamMember { Id = users[0].Id, Email = Member.Email, DisplayName = Member.DisplayName, TeamName = Member.TeamName }; await _repository.CreateOrUpdateAsync(member); return(RedirectToPage("TeamMembers")); } if (users.Count == 0) { var message = $"No active users found with email '{Member.Email}'; Count including inactive: {identities.Count}"; _logger.LogWarning(message); ModelState.AddModelError($"{nameof(Member)}.{nameof(Member.Email)}", $"No active users found with email '{Member.Email}'"); } else if (users.Count > 1) { var usersList = string.Join("; ", users.Select(u => $"{u.DisplayName} [{u.Id}]")); var message = $"More than one active user found with email '{Member.Email}'. List: {usersList}"; _logger.LogWarning(message); ModelState.AddModelError($"{nameof(Member)}.{nameof(Member.Email)}", $"More than one active user found with email '{Member.Email}'"); } return(Page()); }
private async Task <QueryDiff> GetQueryHistory(Guid queryId, string project, DateTime from, DateTime to) { var queryUrl = VSTSApiUrl.Create(_config.Value.InstanceName) .ForQueries(project, queryId.ToString()) .WithQueryParameter("$expand", "wiql") .Build(); var wiqlUrl = VSTSApiUrl.Create(_config.Value.InstanceName) .ForWIQL(project) .Build(); var query = await _client.ExecuteGet <VSTSQuery>(queryUrl); var days = (int)to.Date.Subtract(from.Date).TotalDays; var diff = new QueryDiff() { States = new List <QueryState>(days) }; for (int i = 0; i < days; i++) { var date = from.Date.AddDays(i); var modifiedQuery = $"{query.Wiql} ASOF '{date.ToString("yyyy-MM-dd")}'"; var result = await _client.ExecutePost <WorkItemsQueryResponse>(wiqlUrl, new ItemsQuery(modifiedQuery)); var previous = diff.States.LastOrDefault(); var current = new QueryState { Date = date, ItemsCount = result.Count }; if (previous != null) { current.Trend = current.ItemsCount - previous.ItemsCount; } diff.States.Add(current); } return(diff); }