// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Linq.Expressions; using System.Net; using System.Text; using System.Web.Mvc; using System.Xml; using Tools.CrashReporter.CrashReportWebSite.DataModels; using Tools.CrashReporter.CrashReportWebSite.DataModels.Repositories; using Tools.CrashReporter.CrashReportWebSite.Models; using Tools.CrashReporter.CrashReportWebSite.Properties; using Tools.CrashReporter.CrashReportWebSite.ViewModels; using Tools.DotNETCommon; namespace Tools.CrashReporter.CrashReportWebSite.Controllers { // Each time the controller is run, create a new CSV file in the specified folder and add the link and the top of the site, // so it could be easily downloaded // Removed files older that 7 days /* GameName TimeOfCrash BuiltFromCL PlatformName EngineMode MachineId Module BuildVersion Jira Branch CrashType EpicId BuggId */ /// CSV data row. public class FCSVRow { /// GameName. public string GameName; /// TimeOfCrash. public DateTime TimeOfCrash; /// BuiltFromCL. public string BuiltFromCL; /// PlatformName. public string PlatformName; /// EngineMode. public string EngineMode; /// MachineId. public string MachineId; /// Module. public string Module; /// BuildVersion. public string BuildVersion; /// Jira. public string Jira; /// Branch. public string Branch; /// CrashType. public short CrashType; /// EpicId. public string EpicId; /// BuggId. public int BuggId; /// Returns Crash type as a string. public string GetCrashTypeAsString() { if (CrashType == 1) { return "Crash"; } else if (CrashType == 2) { return "Assert"; } else if (CrashType == 3) { return "Ensure"; } return "Unknown"; } } /// /// The controller to handle graphing of Crashes per user group over time. /// public class CSVController : Controller { /// Fake id for all user groups public static readonly int AllUserGroupId = -1; static readonly int MinNumberOfCrashes = 2; //Ugly instantiation of Crash repository will replace with dependency injection BEFORE this gets anywhere near live. private IUnitOfWork _unitOfWork; public CSVController(IUnitOfWork unitOfWork) { _unitOfWork = unitOfWork; } /// /// Retrieve all Buggs matching the search criteria. /// /// The incoming form of search criteria from the client. /// A view to display the filtered Buggs. public CSV_ViewModel GetResults( FormHelper FormData ) { using( var logTimer = new FAutoScopedLogTimer( this.GetType().ToString() ) ) { var anonymousGroup = _unitOfWork.UserGroupRepository.First(data => data.Name == "Anonymous"); var anonymousGroupId = anonymousGroup.Id; var anonumousIDs = new HashSet(anonymousGroup.Users.Select(data => data.Id)); var anonymousId = anonumousIDs.First(); var userNamesForUserGroup = new HashSet(anonymousGroup.Users.Select(data => data.UserName)); // Enable to narrow results and improve debugging performance. //FormData.DateFrom = FormData.DateTo.AddDays( -1 ); //FormData.DateTo = FormData.DateTo.AddDays( 1 ); var filteringQueryJoin = _unitOfWork.CrashRepository .ListAll() .Where(c => c.EpicAccountId != "") // Only Crashes and asserts .Where(c => c.CrashType == 1 || c.CrashType == 2) // Only anonymous user .Where(c => c.UserId == anonymousId) // Filter be date .Where(c => c.TimeOfCrash > FormData.DateFrom && c.TimeOfCrash < FormData.DateTo) .Select ( c => new { GameName = c.GameName, TimeOfCrash = c.TimeOfCrash, BuiltFromCL = c.ChangelistVersion, PlatformName = c.PlatformName, EngineMode = c.EngineMode, MachineId = c.ComputerName, Module = c.Module, BuildVersion = c.BuildVersion, Jira = c.Jira, Branch = c.Branch, CrashType = c.CrashType, EpicId = c.EpicAccountId, BuggId = c.Bugg.Id } ); var filteringQueryCrashes = _unitOfWork.CrashRepository .ListAll() .Where(c => c.EpicAccountId != null) // Only Crashes and asserts .Where(c => c.CrashType == 1 || c.CrashType == 2) // Only anonymous user .Where(c => c.UserId == anonymousId);//44 //Server timeout var totalCrashes = _unitOfWork.CrashRepository.ListAll().Count(); var startOfYear = new DateTime(DateTime.UtcNow.Year, 1, 1); var totalCrashesYearToDate = _unitOfWork.CrashRepository .ListAll().Count(c => c.TimeOfCrash > startOfYear); var CrashesFilteredWithDateQuery = filteringQueryCrashes // Filter be date .Where(c => c.TimeOfCrash > FormData.DateFrom && c.TimeOfCrash < FormData.DateTo); int CrashesFilteredWithDate = CrashesFilteredWithDateQuery .Count(); int CrashesYearToDateFiltered = filteringQueryCrashes.Count(c => c.TimeOfCrash > startOfYear); //DB server timeout int AffectedUsersFiltered = filteringQueryCrashes.Select( c => c.EpicAccountId ).Distinct().Count(); //DB server time out int UniqueCrashesFiltered = filteringQueryCrashes.Select( c => c.Pattern ).Count(); int NumCrashes = filteringQueryJoin.Count(); // Export data to the file. string CSVPathname = Path.Combine(Settings.Default.CrashReporterCSV, DateTime.UtcNow.ToString("yyyy-MM-dd.HH-mm-ss")); CSVPathname += string .Format("__[{0}---{1}]__{2}", FormData.DateFrom.ToString("yyyy-MM-dd"), FormData.DateTo.ToString("yyyy-MM-dd"), NumCrashes) + ".csv"; var serverPath = Server.MapPath(CSVPathname); var csvFile = new StreamWriter(serverPath, true, Encoding.UTF8); using (var exportToCsvTimer = new FAutoScopedLogTimer("ExportToCSV")) { var rowType = filteringQueryJoin.FirstOrDefault().GetType(); string AllProperties = ""; foreach (var Property in rowType.GetProperties()) { AllProperties += Property.Name; AllProperties += "; "; } // Write header csvFile.WriteLine(AllProperties); foreach (var Row in filteringQueryJoin) { var BVParts = Row.BuildVersion.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries); if (BVParts.Length > 2 && BVParts[0] != "0") { var cleanEngineVersion = string.Format("{0}.{1}.{2}", BVParts[0], BVParts[1], BVParts[2]); var rowProperties = new string[] { Row.GameName, Row.TimeOfCrash.ToString(), Row.BuiltFromCL, Row.PlatformName, Row.EngineMode, Row.MachineId, Row.Module, cleanEngineVersion, Row.Jira, Row.Branch, Row.CrashType == 1 ? "Crash" : "Assert", Row.EpicId, Row.BuggId.ToString() }; var joinedLine = string.Join("; ", rowProperties); joinedLine += "; "; csvFile.WriteLine(joinedLine); } } csvFile.Flush(); csvFile.Close(); csvFile = null; } List CSVRows = filteringQueryJoin .OrderByDescending(X => X.TimeOfCrash) .Take(32) .Select(c => new FCSVRow { GameName = c.GameName, TimeOfCrash = c.TimeOfCrash, BuiltFromCL = c.BuiltFromCL, PlatformName = c.PlatformName, EngineMode = c.EngineMode, MachineId = c.MachineId, Module = c.Module, BuildVersion = c.BuildVersion, Jira = c.Jira, Branch = c.Branch, CrashType = c.CrashType, EpicId = c.EpicId, BuggId = c.BuggId, }) .ToList(); return new CSV_ViewModel() { CSVRows = CSVRows, CSVPathname = CSVPathname, DateFrom = (long)(FormData.DateFrom - CrashesViewModel.Epoch).TotalMilliseconds, DateTo = (long)(FormData.DateTo - CrashesViewModel.Epoch).TotalMilliseconds, DateTimeFrom = FormData.DateFrom, DateTimeTo = FormData.DateTo, AffectedUsersFiltered = AffectedUsersFiltered, UniqueCrashesFiltered = UniqueCrashesFiltered, CrashesFilteredWithDate = CrashesFilteredWithDate, TotalCrashes = totalCrashes, TotalCrashesYearToDate = totalCrashesYearToDate, }; } } /// /// /// /// public ActionResult Index( FormCollection Form ) { using( var LogTimer = new FAutoScopedLogTimer( this.GetType().ToString(), bCreateNewLog: true ) ) { var model = new CSV_ViewModel(); model.DateTimeFrom = DateTime.Now.AddDays(-7); model.DateTimeTo = DateTime.Now; model.DateFrom = (long) (model.DateTimeFrom - CrashesViewModel.Epoch).TotalMilliseconds; model.DateTo = (long) (model.DateTimeTo - CrashesViewModel.Epoch).TotalMilliseconds; return View("Index", model); } } /// /// /// /// /// public ActionResult GenerateCSV(FormCollection Form) { using (var logTimer = new FAutoScopedLogTimer(this.GetType().ToString(), bCreateNewLog: true)) { var formData = new FormHelper(Request, Form, "JustReport"); var results = GetResults(formData); results.GenerationTime = logTimer.GetElapsedSeconds().ToString("F2"); return View("CSV", results); } } /// /// Holding on to entity contexts for too long causes desynchronisation errors /// so dispose of the context when we're done with this controller /// /// protected override void Dispose(bool disposing) { _unitOfWork.Dispose(); base.Dispose(disposing); } } }