public async Task <TfsQueryResult> Query(TfsKnownConnection knownConn, TfsStatesViewModel model) { var sw = Stopwatch.StartNew(); var vssConnection = knownConn.ToVssConnection(); this.workItemClient = vssConnection.GetClient <WorkItemTrackingHttpClient>(); var wiql = TfsQueryBuilder.BuildQuery(model); var tfsQueryResult = await this.workItemClient.QueryByWiqlAsync(new Wiql { Query = wiql }); var workItems = tfsQueryResult.WorkItems.ToList(); this.totalCount = workItems.Count(); var queue = new ConcurrentQueue <TfsInfo>(); var asyncOptions = GetAsyncOptions(); var getRevsBlock = new ActionBlock <WorkItemReference>( async workItemRef => { var tfsInfo = await ProcessWorkItemRevisions(workItemRef); if (tfsInfo.TransitionCount > model.MinTransitions) { queue.Enqueue(tfsInfo); } }, asyncOptions); foreach (var wiRef in workItems) { getRevsBlock.Post(wiRef); } getRevsBlock.Complete(); await getRevsBlock.Completion; var list = queue .OrderBy(i => i.Iteration) .ThenBy(i => i.Id) .ToList(); sw.Stop(); var result = new TfsQueryResult { TfsItems = list, TotalWorkItems = this.processedCount, TotalRevisions = this.revisionsCount }; return(result); }
public Chart CreateBarChart(TfsQueryResult queryResult) { Chart chart = new Chart { Type = "bar" }; Data data = new Data { Labels = new List <string>() }; var counts = queryResult .TfsItems .GroupBy(t => t.TransitionCount) .OrderBy(t => t.Key) .Select(c => c.Key) .ToList(); var rawData = new List <double>(); foreach (var count in counts) { data.Labels.Add($"{"cycle".ToQuantity(count)}"); var itemCount = queryResult.TfsItems.Count(t => t.TransitionCount == count); rawData.Add(itemCount); } BarDataset dataset = new BarDataset() { Label = "# work items with cycle", Data = rawData, BackgroundColor = new List <string>() { "rgba(255, 99, 132, 0.2)", "rgba(54, 162, 235, 0.2)", "rgba(255, 206, 86, 0.2)", "rgba(75, 192, 192, 0.2)", "rgba(153, 102, 255, 0.2)", "rgba(255, 159, 64, 0.2)" }, BorderColor = new List <string>() { "rgba(255,99,132,1)", "rgba(54, 162, 235, 1)", "rgba(255, 206, 86, 1)", "rgba(75, 192, 192, 1)", "rgba(153, 102, 255, 1)", "rgba(255, 159, 64, 1)" }, BorderWidth = new List <int>() { 1 }, }; data.Datasets = new List <Dataset> { dataset }; chart.Data = data; BarOptions options = new BarOptions() { Scales = new Scales(), BarPercentage = 0.7, Title = new Title { Display = true, Text = "Frequency of Cycles (transitions)" }, }; Scales scales = new Scales() { YAxes = new List <Scale>() { new CartesianScale() { Ticks = new CartesianLinearTick() { BeginAtZero = true } } } }; options.Scales = scales; chart.Options = options; chart.Options.Layout = new Layout() { Padding = new Padding() { PaddingObject = new PaddingObject() { Left = 10, Right = 12 } } }; return(chart); }
public async Task <IActionResult> RunReport(TfsStatesViewModel viewModel) { if (viewModel.ConnectionId == Guid.Empty) { ModelState.AddModelError(nameof(viewModel.ConnectionId), "Connection is required"); } if (string.IsNullOrEmpty(viewModel.Project) || viewModel.Project == NoProjectSelected) { ModelState.AddModelError(nameof(viewModel.Project), "Project is required"); } if (string.IsNullOrEmpty(viewModel.Iteration) || viewModel.Iteration == NoIterationSelected) { ModelState.AddModelError( nameof(viewModel.Iteration), "Iteration Under is required (you can select a parent iteration container)"); } await LoadLookups(viewModel); if (!ModelState.IsValid) { return(View(ViewName, viewModel)); } var sw = Stopwatch.StartNew(); await FileUtility.Cleanup(); TfsQueryResult queryResult = null; var knownConn = await this.settingsService.GetConnection(viewModel.ConnectionId); try { SendProgress($"Querying project {viewModel.Project}, iteration under {viewModel.Iteration}..."); queryResult = await this.tfsQueryService.Query(knownConn, viewModel); } catch (Exception ex) { Trace.WriteLine(ex); var settingsUrl = Url.Action("Index", "Settings"); viewModel.RunReadyState.NotReady( $"Error querying TFS. Verify <a href='{settingsUrl}'>TFS settings</a> " + $"and your connectivity and try again."); return(View(ViewName, viewModel)); } if (queryResult.TfsItems.Count > 0) { var fName = $"TfsStates_{DateTime.Now.Ticks}.xlsx"; var filename = await FileUtility.GetFilename(fName); SendProgress($"Writing {filename}..."); var projectUrl = $"{knownConn.Url}/{viewModel.Project}"; this.excelWriterService.Write(filename, queryResult.TfsItems, projectUrl); SendProgress($"Launching {filename}..."); System.Threading.Thread.Sleep(1000); viewModel.ResultFilename = fName; await Electron.Shell.OpenExternalAsync(filename); } // eat file in use exception try { await this.reportHistoryService.Record(knownConn.Id, viewModel.Project, viewModel.Iteration); } catch (IOException ioEx) { } if (queryResult.TfsItems.Count > 0) { var chart = chartService.CreateBarChart(queryResult); ViewData["chart"] = chart; } sw.Stop(); var items = queryResult.TfsItems; var totalTransitions = items.Sum(x => x.TransitionCount); var avgTransitions = items.Count > 0 ? Math.Round(items.Average(x => x.TransitionCount), 0) : 0; viewModel.FinalProgress = new ReportProgress { WorkItemsProcessed = queryResult.TotalWorkItems, Message = $"Processed {"work item".ToQuantity(queryResult.TotalWorkItems, "###,##0")} and " + $"{"revision".ToQuantity(queryResult.TotalRevisions, "###,##0")} in {sw.Elapsed.Humanize()}. " + $"{"transition".ToQuantity(totalTransitions, "###,##0")}. " + $"Average work item transitions: {avgTransitions}" }; return(View(ViewName, viewModel)); }