protected override async Task <ReportResult> GenerateAsync(GenerateReOpenedWorkItemsReport command, IDataSource dataSource, ProfileViewModel profile)
        {
            // TODO: This part is duplicated between work item reporters, move to base class
            if (profile.Members == null || !profile.Members.Any())
            {
                Logger.LogWarning("Profile '{ProfileName}({Profile})' does not have any members.", profile.Name, profile.Id);
                return(WorkItemsReport.Empty);
            }

            var workItems = await GetAllWorkItems(dataSource, profile.Members);

            if (!workItems.Any())
            {
                Logger.LogWarning("No work items found for members in '{ProfileName}({Profile})'", profile.Name, profile.Id);
                return(WorkItemsReport.Empty);
            }

            var team = await GetAllTeamMembers(dataSource, profile.Members);

            var scope = new ClassificationScope(team, command.Start, command.End);

            var resolvedList = new List <(string email, int id)>(workItems.Count);
            var report       = new ReOpenedWorkItemsReport();

            foreach (var workItem in workItems)
            {
                try
                {
                    var resolutions = _workItemClassificationContext.Classify(workItem, scope);
                    var details     = resolutions.OfType <WorkItemReOpenedEvent>()
                                      .Select(e => new ReOpenedWorkItemDetail
                    {
                        WorkItemId      = e.WorkItem.Id,
                        WorkItemTitle   = e.WorkItem.Title,
                        WorkItemType    = e.WorkItem.Type,
                        WorkItemProject = string.Empty,
                        ReOpenedDate    = e.Date,
                        AssociatedUser  = e.AssociatedUser
                    });
                    report.Details.AddRange(details);

                    var resolvedByUser = resolutions.OfType <WorkItemResolvedEvent>().Select(e => (e.AssociatedUser.Email, e.WorkItem.Id));
                    resolvedList.AddRange(resolvedByUser);
                }
                catch (Exception ex)
                {
                    Logger.LogError(ex, "Error generating details for workitem {WorkItemId}", workItem.WorkItemId);
                }
            }

            var resolvedLookup = resolvedList
                                 .Distinct()
                                 .GroupBy(r => r.email)
                                 .ToDictionary(k => k.Key, v => v.Count());

            report.ResolvedWorkItemsLookup = resolvedLookup;
            report.MembersLookup           = team.OrderBy(t => t.DisplayName).ToDictionary(k => k.Email, v => v.DisplayName);

            return(report);
        }
Ejemplo n.º 2
0
 public static CodeClassificationBuilder IsInClassificationScope(
     this CodeClassificationBuilder @this,
     ClassificationScope scope)
 {
     return(@this.As <ICodeClassificationBuilder>()
            .IsInClassificationScope(scope));
 }
Ejemplo n.º 3
0
        private async Task <AggregatedWorkitemsETAReport> GenerateReport(GenerateAggregatedWorkitemsETAReport command, IDataSource dataSource, ProfileViewModel profile)
        {
            var workItems = await GetAllWorkItems(dataSource, profile.Members);

            if (!workItems.Any())
            {
                return(AggregatedWorkitemsETAReport.Empty);
            }

            var team = await GetAllTeamMembers(dataSource, profile.Members);

            var scope       = new ClassificationScope(team, command.Start, command.End);
            var resolutions = workItems.SelectMany(w => _workItemClassificationContext.Classify(w, scope))
                              .GroupBy(r => r.AssociatedUser.Email)
                              .ToDictionary(k => k.Key, v => v.AsEnumerable());

            var report = new AggregatedWorkitemsETAReport(team.Count());

            foreach (var member in team)
            {
                var individualReport = GetIndividualReport(resolutions, workItems, dataSource, member, team);
                report.IndividualReports.Add(individualReport);
            }

            return(report);
        }
Ejemplo n.º 4
0
        CodeClassificationBuilder ICodeClassificationBuilder.IsInClassificationScope(
            ClassificationScope scope)
        {
            _scope.EnsureNotAssigned(nameof(scope));
            _scope = scope;

            return(this);
        }
Ejemplo n.º 5
0
        public IEnumerable <WorkItemResolution> Classify(VSTSWorkItem item, ClassificationScope scope)
        {
            var rs = from c in _classifiers
                     let r = c.Classify(new WorkItemResolutionRequest {
                WorkItem = item, Team = scope.Team, StartDate = scope.StartDate, EndDate = scope.EndDate
            })
                             where !r.IsNone && (IsInRange(r, scope) || r.IsError)
                             select r;

            return(rs.ToList());
        }
Ejemplo n.º 6
0
        private async Task <WorkItemsReport> GenerateReport(IDataSource dataSource, ProfileViewModel profile, GenerateWorkItemsReport command)
        {
            if (profile.Members == null || !profile.Members.Any())
            {
                Logger.LogWarning("Profile '{ProfileName}({Profile})' does not have any members.", profile.Name, profile.Id);
                return(WorkItemsReport.Empty);
            }

            var workItems = await GetAllWorkItems(dataSource, profile.Members);

            if (!workItems.Any())
            {
                Logger.LogWarning("No work items found for members in '{ProfileName}({Profile})'", profile.Name, profile.Id);
                return(WorkItemsReport.Empty);
            }

            var team = await GetAllTeamMembers(dataSource, profile.Members);

            var scope = new ClassificationScope(team, command.Start, command.End);

            var report = WorkItemsReport.Empty;

            foreach (var workItem in workItems)
            {
                var isInCodeReview = await dataSource.IsInCodeReview(workItem);

                if (isInCodeReview && dataSource.IsAssignedToTeamMember(workItem, team))
                {
                    report.WorkItemsInReview.Add(dataSource.CreateWorkItemDetail(workItem, team));
                }
                else if (dataSource.IsActive(workItem) && dataSource.IsAssignedToTeamMember(workItem, team))
                {
                    report.ActiveWorkItems.Add(dataSource.CreateWorkItemDetail(workItem, team));
                }
                else
                {
                    var resolutions = _workItemClassificationContext.Classify(workItem, scope);
                    if (dataSource.IsResolved(resolutions))
                    {
                        report.ResolvedWorkItems.Add(dataSource.CreateWorkItemDetail(workItem, team));
                    }
                }
            }

            return(report);
        }
Ejemplo n.º 7
0
 private bool IsInRange(WorkItemResolution r, ClassificationScope scope)
 {
     return((r.ResolutionDate >= scope.StartDate && r.ResolutionDate <= scope.EndDate) ||
            r.ResolutionDate == VSTSMaxDate);
 }
        protected override async Task <ReportResult> ReportInternal()
        {
            if (!Input.Members.Any() || !Input.Repositories.Any())
            {
                return(AggregatedWorkitemsETAReport.Empty);
            }

            var settings = await _repository.GetSingleAsync <Settings>(_ => true);

            var etaFields = settings?.WorkItemsSettings?.ETAFields;

            if (etaFields == null || !etaFields.Any())
            {
                throw new MissingETASettingsException();
            }

            await _progressReporter.Report("Fetching workitems...");

            var workItemIds = Input.Members.SelectMany(m => m.RelatedWorkItemIds);
            var workitems   = await _repository.GetAsync <VSTSWorkItem>(w => workItemIds.Contains(w.WorkItemId));

            if (!workitems.Any())
            {
                return(AggregatedWorkitemsETAReport.Empty);
            }

            await _progressReporter.Report("Looking for work item resolutions...");

            var scope       = new ClassificationScope(Input.Members, Input.Query.StartDate, Input.ActualEndDate);
            var resolutions = workitems.SelectMany(w => _classificationContext.Classify(w, scope))
                              .Where(r => r.Resolution == WorkItemStates.Resolved || (r.WorkItemType == WorkItemTypes.Task && r.Resolution == WorkItemStates.Closed))
                              .GroupBy(r => r.MemberEmail)
                              .ToDictionary(k => k.Key, v => v.AsEnumerable());

            var result = new AggregatedWorkitemsETAReport();

            result.IndividualReports = new List <AggregatedWorkitemsETAReport.IndividualETAReport>(Input.Members.Count());
            foreach (var member in Input.Members)
            {
                await _progressReporter.Report($"Calculating metrics for {member.DisplayName}", GetProgressStep());

                var individualReport = GetIndividualReport(member);
                result.IndividualReports.Add(individualReport);
            }

            return(result);

            // Local methods
            AggregatedWorkitemsETAReport.IndividualETAReport GetIndividualReport(TeamMember member)
            {
                if (!resolutions.ContainsKey(member.Email))
                {
                    return(AggregatedWorkitemsETAReport.IndividualETAReport.GetEmptyFor(member));
                }

                var individualReport = new AggregatedWorkitemsETAReport.IndividualETAReport
                {
                    MemberEmail = member.Email,
                    MemberName  = member.DisplayName
                };

                PopulateMetrics(member.Email, individualReport);

                return(individualReport);
            }

            void PopulateMetrics(string email, AggregatedWorkitemsETAReport.IndividualETAReport report)
            {
                var resolved = resolutions[email];

                report.TotalResolved      = resolved.Count();
                report.TotalResolvedBugs  = resolved.Count(w => string.Equals(w.WorkItemType, "Bug", StringComparison.OrdinalIgnoreCase));
                report.TotalResolvedTasks = resolved.Count(w => string.Equals(w.WorkItemType, "Task", StringComparison.OrdinalIgnoreCase));
                report.Details            = new List <AggregatedWorkitemsETAReport.IndividualReportDetail>(report.TotalResolved);
                foreach (var item in resolved)
                {
                    var workitem         = workitems.Single(w => w.WorkItemId == item.WorkItemId);
                    var timeSpent        = GetActiveDuration(workitem);
                    var originalEstimate = GetEtaValue(workitem, ETAFieldType.OriginalEstimate);
                    var completedWork    = GetEtaValue(workitem, ETAFieldType.CompletedWork);
                    var remainingWork    = GetEtaValue(workitem, ETAFieldType.RemainingWork);
                    if (IsETAEmpty(workitem))
                    {
                        report.WithoutETA++;
                        report.CompletedWithoutEstimates += timeSpent;
                    }
                    else
                    {
                        var estimatedByDev = completedWork + remainingWork;
                        if (estimatedByDev == 0)
                        {
                            estimatedByDev = originalEstimate;
                        }

                        if (originalEstimate != 0)
                        {
                            report.WithOriginalEstimate++;
                        }

                        report.OriginalEstimated   += originalEstimate;
                        report.EstimatedToComplete += estimatedByDev;

                        report.CompletedWithEstimates += timeSpent;
                    }

                    report.Details.Add(new AggregatedWorkitemsETAReport.IndividualReportDetail
                    {
                        WorkItemId          = item.WorkItemId,
                        WorkItemTitle       = item.WorkItemTitle,
                        WorkItemType        = item.WorkItemType,
                        OriginalEstimate    = originalEstimate,
                        EstimatedToComplete = remainingWork + completedWork,
                        TimeSpent           = timeSpent,
                    });
                }
            }

            bool IsETAEmpty(VSTSWorkItem wi) =>
            IsNullOrEmpty(wi, FieldNameFor(wi.WorkItemType, ETAFieldType.OriginalEstimate)) &&
            IsNullOrEmpty(wi, FieldNameFor(wi.WorkItemType, ETAFieldType.CompletedWork)) &&
            IsNullOrEmpty(wi, FieldNameFor(wi.WorkItemType, ETAFieldType.RemainingWork));

            bool IsNullOrEmpty(VSTSWorkItem wi, string fieldName) => !wi.Fields.ContainsKey(fieldName) || string.IsNullOrEmpty(wi.Fields[fieldName]);

            string FieldNameFor(string workItemType, ETAFieldType fieldType) => etaFields.First(f => f.WorkitemType == workItemType && f.FieldType == fieldType).FieldName;

            float GetEtaValue(VSTSWorkItem wi, ETAFieldType etaType)
            {
                var fieldName = FieldNameFor(wi.WorkItemType, etaType);

                if (!wi.Fields.ContainsKey(fieldName))
                {
                    return(0);
                }

                var value = wi.Fields[fieldName];

                if (string.IsNullOrEmpty(value))
                {
                    return(0);
                }

                return(float.Parse(value));
            }
        }
Ejemplo n.º 9
0
        protected override async Task <ReportResult> ReportInternal()
        {
            if (!Input.Members.Any() || !Input.Repositories.Any())
            {
                return(WeeklyStatusReport.Empty);
            }

            var settings = await _repository.GetSingleAsync <Settings>(_ => true);

            var etaFields = settings?.WorkItemsSettings?.ETAFields;

            if (etaFields == null || !etaFields.Any())
            {
                throw new MissingETASettingsException();
            }

            await _progressReporter.Report("Fetching workitems...");

            var workItemIds = Input.Members.SelectMany(m => m.RelatedWorkItemIds);
            var workitems   = (await _repository.GetAsync <VSTSWorkItem>(w => workItemIds.Contains(w.WorkItemId))).Where(w => w.WorkItemType != null && (w.WorkItemType == WorkItemTypes.Bug || w.WorkItemType == WorkItemTypes.Task));

            if (!workitems.Any())
            {
                return(WeeklyStatusReport.Empty);
            }

            await _progressReporter.Report("Looking for work item resolutions...");

            var scope = new ClassificationScope(Input.Members, Input.Query.StartDate, Input.ActualEndDate);

            var report = WeeklyStatusReport.Empty;

            foreach (var workItem in workitems)
            {
                if (workItem.IsInCodeReview() && workItem.IsAssignedToTeamMember(Input.Members))
                {
                    report.WorkItemsInReview.Add(CreateWorkItemDetail(workItem));
                }
                else if (workItem.State == WorkItemStates.Active && workItem.IsAssignedToTeamMember(Input.Members))
                {
                    report.ActiveWorkItems.Add(CreateWorkItemDetail(workItem));
                }
                else
                {
                    var resolutions = _classificationContext.Classify(workItem, scope);
                    if (resolutions.Any(r => r.Resolution == WorkItemStates.Resolved || (r.WorkItemType == WorkItemTypes.Task && r.Resolution == WorkItemStates.Closed)))
                    {
                        report.ResolvedWorkItems.Add(CreateWorkItemDetail(workItem));;
                    }
                }
            }

            WeeklyStatusReport.WorkItemDetail CreateWorkItemDetail(VSTSWorkItem item)
            {
                var timeSpent        = item.GetActiveDuration(Input.Members);
                var originalEstimate = item.GetEtaValue(ETAFieldType.OriginalEstimate, settings);
                var completedWork    = item.GetEtaValue(ETAFieldType.CompletedWork, settings);
                var remainingWork    = item.GetEtaValue(ETAFieldType.RemainingWork, settings);

                if (remainingWork < 1)
                {
                    remainingWork = originalEstimate;
                }

                return(new WeeklyStatusReport.WorkItemDetail
                {
                    WorkItemId = item.WorkItemId,
                    WorkItemTitle = item.Title,
                    WorkItemType = item.WorkItemType,
                    WorkItemProject = string.IsNullOrWhiteSpace(item.AreaPath) ? null : item.AreaPath.Split('\\')[0],
                    Tags = item.Updates.LastOrDefault(u => !string.IsNullOrWhiteSpace(u.Tags.NewValue))?.Tags?.NewValue,
                    OriginalEstimate = originalEstimate,
                    EstimatedToComplete = remainingWork + completedWork,
                    TimeSpent = timeSpent
                });
            }

            return(report);
        }