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