Beispiel #1
0
        private void PopulateMetrics(
            Dictionary <string, IEnumerable <IWorkItemEvent> > resolutions,
            IEnumerable <WorkItemViewModel> workItems,
            IDataSource dataSource,
            string email,
            AggregatedWorkitemsETAReport.IndividualETAReport report,
            IEnumerable <TeamMemberViewModel> team)
        {
            var resolvedByMember = resolutions[email];

            report.TotalResolved      = resolvedByMember.Count();
            report.TotalResolvedBugs  = resolvedByMember.Count(w => string.Equals(w.WorkItem.Type, "Bug", StringComparison.OrdinalIgnoreCase));
            report.TotalResolvedTasks = resolvedByMember.Count(w => string.Equals(w.WorkItem.Type, "Task", StringComparison.OrdinalIgnoreCase));
            report.Details            = new List <AggregatedWorkitemsETAReport.IndividualReportDetail>(report.TotalResolved);
            foreach (var item in resolvedByMember)
            {
                var workitem  = workItems.Single(w => w.WorkItemId == item.WorkItem.Id);
                var timeSpent = dataSource.GetActiveDuration(workitem, team);
                var eta       = dataSource.GetETAValues(workitem);
                if (eta.IsEmpty)
                {
                    report.WithoutETA++;
                    report.CompletedWithoutEstimates += timeSpent;
                }
                else
                {
                    var estimatedByDev = eta.CompletedWork + eta.RemainingWork;
                    if (estimatedByDev == 0)
                    {
                        estimatedByDev = eta.OriginalEstimate;
                    }

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

                    report.OriginalEstimated   += eta.OriginalEstimate;
                    report.EstimatedToComplete += estimatedByDev;

                    report.CompletedWithEstimates += timeSpent;
                }

                report.Details.Add(new AggregatedWorkitemsETAReport.IndividualReportDetail
                {
                    WorkItemId          = item.WorkItem.Id,
                    WorkItemTitle       = item.WorkItem.Title,
                    WorkItemType        = item.WorkItem.Type,
                    OriginalEstimate    = eta.OriginalEstimate,
                    EstimatedToComplete = eta.RemainingWork + eta.CompletedWork,
                    TimeSpent           = timeSpent,
                });
            }
        }
Beispiel #2
0
        private AggregatedWorkitemsETAReport.IndividualETAReport GetIndividualReport(
            Dictionary <string, IEnumerable <IWorkItemEvent> > resolutions,
            IEnumerable <WorkItemViewModel> workItems,
            IDataSource dataSource,
            TeamMemberViewModel member,
            IEnumerable <TeamMemberViewModel> team)
        {
            if (!resolutions.ContainsKey(member.Email))
            {
                return(AggregatedWorkitemsETAReport.IndividualETAReport.GetEmptyFor(member));
            }

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

            PopulateMetrics(resolutions, workItems, dataSource, member.Email, individualReport, team);

            return(individualReport);
        }
        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));
            }
        }