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