Beispiel #1
0
        public async Task ShouldReturnCorrectReportType()
        {
            const int expectedNumberOfReports = 5;
            var       id = Guid.NewGuid();
            var       individualReports = Builder <AggregatedWorkitemsETAReport.IndividualETAReport>
                                          .CreateListOfSize(expectedNumberOfReports).Build();

            var report = new AggregatedWorkitemsETAReport(expectedNumberOfReports);

            report.IndividualReports.AddRange(individualReports);

            RepositoryMock.Setup(r => r.GetFieldValueAsync(It.IsAny <Expression <Func <ReportResult, bool> > >(), It.IsAny <Expression <Func <ReportResult, string> > >()))
            .ReturnsAsync(Constants.ETAReportType.ToLower())
            .Verifiable();
            RepositoryMock.Setup(r => r.GetSingleAsync(id, It.Is <Type>(t => t == typeof(AggregatedWorkitemsETAReport))))
            .ReturnsAsync(report)
            .Verifiable();

            var result = await _handler.Handle(new GetReportById(id));

            result.Should().NotBeNull();
            result.Should().BeOfType <AggregatedWorkitemsETAReportViewModel>();
            result.As <AggregatedWorkitemsETAReportViewModel>().IndividualReports.Should().HaveCount(expectedNumberOfReports);
            RepositoryMock.Verify();
        }
Beispiel #2
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);
        }
        private void VerifyReportFor(TeamMemberViewModel member, AggregatedWorkitemsETAReport report, ExpectedValuesContainer expectedValues)
        {
            var memberReport = report.IndividualReports.Single(r => r.MemberEmail == member.Email);

            memberReport.TotalResolved.Should().Be(expectedValues.GetValue <int>(nameof(memberReport.TotalResolved)));
            memberReport.CompletedWithEstimates.Should().Be(expectedValues.GetValue <int>(nameof(memberReport.CompletedWithEstimates)));
            memberReport.CompletedWithoutEstimates.Should().Be(expectedValues.GetValue <int>(nameof(memberReport.CompletedWithoutEstimates)));
            memberReport.EstimatedToComplete.Should().Be(expectedValues.GetValue <int>(nameof(memberReport.EstimatedToComplete)));
            memberReport.OriginalEstimated.Should().Be(expectedValues.GetValue <int>(nameof(memberReport.OriginalEstimated)));
            memberReport.WithOriginalEstimate.Should().Be(expectedValues.GetValue <int>(nameof(memberReport.WithOriginalEstimate)));
            memberReport.WithoutETA.Should().Be(expectedValues.GetValue <int>(nameof(memberReport.WithoutETA)));
            memberReport.TotalResolvedBugs.Should().Be(expectedValues.GetValue <int>(nameof(memberReport.TotalResolvedBugs)));
            memberReport.TotalResolvedTasks.Should().Be(expectedValues.GetValue <int>(nameof(memberReport.TotalResolvedTasks)));
            memberReport.MemberEmail.Should().Be(expectedValues.GetValue <string>(nameof(memberReport.MemberEmail)));
            memberReport.MemberName.Should().Be(expectedValues.GetValue <string>(nameof(memberReport.MemberName)));
            memberReport.Details.Should().HaveCount(expectedValues.GetValue <int>(nameof(memberReport.Details)));
        }
        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));
            }
        }