/// <summary> /// The Index action. /// </summary> /// <param name="BuggsForm">The form of user data passed up from the client.</param> /// <returns>The view to display a list of Buggs on the client.</returns> public ActionResult Index( FormCollection BuggsForm ) { using( FAutoScopedLogTimer LogTimer = new FAutoScopedLogTimer( this.GetType().ToString() ) ) { FormHelper FormData = new FormHelper( Request, BuggsForm, "CrashesInTimeFrameGroup" ); BuggsViewModel Results = LocalBuggRepository.GetResults( FormData ); return View( "Index", Results ); } }
/* /// <summary> /// The empty view of the buggs page. /// </summary> /// public ActionResult Index() { using (FAutoScopedLogTimer LogTimer = new FAutoScopedLogTimer( this.GetType().ToString(), bCreateNewLog: true )) { BuggsViewModel Results = new BuggsViewModel(); Results.GenerationTime = LogTimer.GetElapsedSeconds().ToString( "F2" ); return View( "Index", Results ); } }*/ /// <summary> /// The Index action. /// </summary> /// <param name="BuggsForm">The form of user data passed up from the client.</param> /// <returns>The view to display a list of Buggs on the client.</returns> public ActionResult Index( FormCollection BuggsForm ) { using (FAutoScopedLogTimer LogTimer = new FAutoScopedLogTimer( this.GetType().ToString(), bCreateNewLog: true )) { BuggRepository Buggs = new BuggRepository(); FormHelper FormData = new FormHelper( Request, BuggsForm, "CrashesInTimeFrameGroup" ); BuggsViewModel Results = Buggs.GetResults( FormData ); foreach (var Bugg in Results.Results) { // Populate function calls. Bugg.GetFunctionCalls(); } Results.GenerationTime = LogTimer.GetElapsedSeconds().ToString( "F2" ); return View( "Index", Results ); } }
/// <summary> /// Display a summary list of crashes based on the search criteria. /// </summary> /// <param name="CrashesForm">A form of user data passed up from the client.</param> /// <returns>A view to display a list of crash reports.</returns> public ActionResult Index( FormCollection CrashesForm ) { using( FAutoScopedLogTimer LogTimer = new FAutoScopedLogTimer( this.GetType().ToString() ) ) { // Handle any edits made in the Set form fields foreach( var Entry in CrashesForm ) { int Id = 0; if( int.TryParse( Entry.ToString(), out Id ) ) { Crash CurrentCrash = LocalCrashRepository.GetCrash( Id ); if( CurrentCrash != null ) { if( !string.IsNullOrEmpty( CrashesForm["SetStatus"] ) ) { CurrentCrash.Status = CrashesForm["SetStatus"]; } if( !string.IsNullOrEmpty( CrashesForm["SetFixedIn"] ) ) { CurrentCrash.FixedChangeList = CrashesForm["SetFixedIn"]; } if( !string.IsNullOrEmpty( CrashesForm["SetTTP"] ) ) { CurrentCrash.TTPID = CrashesForm["SetTTP"]; } } } LocalCrashRepository.SubmitChanges(); } // <STATUS> // Parse the contents of the query string, and populate the form FormHelper FormData = new FormHelper( Request, CrashesForm, "TimeOfCrash" ); CrashesViewModel Result = LocalCrashRepository.GetResults( FormData ); // Add the FromCollection to the CrashesViewModel since we don't need it for the get results function but we do want to post it back to the page. Result.FormCollection = CrashesForm; return View( "Index", Result ); } }
/// <summary> /// Retrieve all Buggs matching the search criteria. /// </summary> /// <param name="FormData">The incoming form of search criteria from the client.</param> /// <returns>A view to display the filtered Buggs.</returns> public BuggsViewModel GetResults( FormHelper FormData ) { using( FAutoScopedLogTimer LogTimer = new FAutoScopedLogTimer( this.GetType().ToString() ) ) { // Right now we take a Result IQueryable starting with ListAll() Buggs then we widdle away the result set by tacking on // Linq queries. Essentially it's Results.ListAll().Where().Where().Where().Where().Where().Where() // Could possibly create more optimized queries when we know exactly what we're querying // The downside is that if we add more parameters each query may need to be updated.... Or we just add new case statements // The other downside is that there's less code reuse, but that may be worth it. IQueryable<Bugg> Results = null; int Skip = ( FormData.Page - 1 ) * FormData.PageSize; int Take = FormData.PageSize; // Get every Bugg. Results = ListAll(); // Look at all Buggs that are still 'open' i.e. the last crash occurred in our date range. Results = FilterByDate( Results, FormData.DateFrom, FormData.DateTo ); // Filter results by build version. Results = FilterByBuildVersion( Results, FormData.BuildVersion ); // Run at the end if( !string.IsNullOrEmpty( FormData.SearchQuery ) ) { Results = Search( Results, FormData.SearchQuery ); } // Filter by Crash Type if( FormData.CrashType != "All" ) { switch( FormData.CrashType ) { case "Crashes": Results = Results.Where( BuggInstance => BuggInstance.CrashType == 1 ); break; case "Assert": Results = Results.Where( BuggInstance => BuggInstance.CrashType == 2 ); break; case "Ensure": Results = Results.Where( BuggInstance => BuggInstance.CrashType == 3 ); break; case "CrashesAsserts": Results = Results.Where( BuggInstance => BuggInstance.CrashType == 1 || BuggInstance.CrashType == 2 ); break; } } // Get UserGroup ResultCounts Dictionary<string, int> GroupCounts = GetCountsByGroup( Results ); // Filter by user group if present Results = FilterByUserGroup( Results, FormData.UserGroup ); // Pass in the results and return them sorted properly IEnumerable<Bugg> SortedResults = GetSortedResults( Results, FormData.SortTerm, ( FormData.SortOrder == "Descending" ), FormData.DateFrom, FormData.DateTo ); // Grab just the results we want to display on this page SortedResults = SortedResults.Skip( Skip ).Take( Take ).ToList(); return new BuggsViewModel { Results = SortedResults, PagingInfo = new PagingInfo { CurrentPage = FormData.Page, PageSize = FormData.PageSize, TotalResults = Results.Count() }, SortTerm = FormData.SortTerm, SortOrder = FormData.SortOrder, UserGroup = FormData.UserGroup, CrashType = FormData.CrashType, SearchQuery = FormData.SearchQuery, DateFrom = (long)( FormData.DateFrom - CrashesViewModel.Epoch ).TotalMilliseconds, DateTo = (long)( FormData.DateTo - CrashesViewModel.Epoch ).TotalMilliseconds, BuildVersion = FormData.BuildVersion, GroupCounts = GroupCounts, }; } }
//static readonly int MinNumberOfCrashes = 2; /// <summary> /// Retrieve all Buggs matching the search criteria. /// </summary> /// <param name="FormData">The incoming form of search criteria from the client.</param> /// <returns>A view to display the filtered Buggs.</returns> public CSV_ViewModel GetResults( FormHelper FormData ) { BuggRepository BuggsRepo = new BuggRepository(); CrashRepository CrashRepo = new CrashRepository(); using( FAutoScopedLogTimer LogTimer = new FAutoScopedLogTimer( this.GetType().ToString() ) ) { string AnonymousGroup = "Anonymous"; //List<String> Users = FRepository.Get().GetUserNamesFromGroupName( AnonumousGroup ); int AnonymousGroupID = FRepository.Get( BuggsRepo ).FindOrAddGroup( AnonymousGroup ); HashSet<int> AnonumousIDs = FRepository.Get( BuggsRepo ).GetUserIdsFromUserGroup( AnonymousGroup ); int AnonymousID = AnonumousIDs.First(); HashSet<string> UserNamesForUserGroup = FRepository.Get( BuggsRepo ).GetUserNamesFromGroupName( AnonymousGroup ); // Enable to narrow results and improve debugging performance. //FormData.DateFrom = FormData.DateTo.AddDays( -1 ); FormData.DateTo = FormData.DateTo.AddDays( 1 ); var FilteringQueryJoin = CrashRepo .ListAll() .Where( c => c.EpicAccountId != "" ) // Only crashes and asserts .Where( c => c.CrashType == 1 || c.CrashType == 2 ) // Only anonymous user .Where( c => c.UserNameId == AnonymousID ) // Filter be date .Where( c => c.TimeOfCrash > FormData.DateFrom && c.TimeOfCrash < FormData.DateTo ) .Join ( CrashRepo.Context.Buggs_Crashes, c => c, bc => bc.Crash, ( c, bc ) => new { GameName = c.GameName, TimeOfCrash = c.TimeOfCrash.Value, 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.Value, EpicId = c.EpicAccountId, BuggId = bc.BuggId, } ); var FilteringQueryCrashes = CrashRepo .ListAll() .Where( c => c.EpicAccountId != "" ) // Only crashes and asserts .Where( c => c.CrashType == 1 || c.CrashType == 2 ) // Only anonymous user .Where( c => c.UserNameId == AnonymousID ); int TotalCrashes = CrashRepo .ListAll() .Count(); int TotalCrashesYearToDate = CrashRepo .ListAll() // Year to date .Where( c => c.TimeOfCrash > new DateTime( DateTime.UtcNow.Year, 1, 1 ) ) .Count(); var CrashesFilteredWithDateQuery = FilteringQueryCrashes // Filter be date .Where( c => c.TimeOfCrash > FormData.DateFrom && c.TimeOfCrash < FormData.DateTo ); int CrashesFilteredWithDate = CrashesFilteredWithDateQuery .Count(); int CrashesYearToDateFiltered = FilteringQueryCrashes // Year to date .Where( c => c.TimeOfCrash > new DateTime( DateTime.UtcNow.Year, 1, 1 ) ) .Count(); int AffectedUsersFiltered = FilteringQueryCrashes .Select( c => c.EpicAccountId ) .Distinct() .Count(); int UniqueCrashesFiltered = FilteringQueryCrashes .Select( c => c.Pattern ) .Distinct() .Count(); int NumCrashes = FilteringQueryJoin.Count(); // Get all users // SLOW //var Users = FRepository.Get( BuggsRepo ).GetAnalyticsUsers().ToDictionary( X => X.EpicAccountId, Y => Y ); // 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"; string ServerPath = Server.MapPath( CSVPathname ); var CSVFile = new StreamWriter( ServerPath, true, Encoding.UTF8 ); using (FAutoScopedLogTimer 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") { string CleanEngineVersion = string.Format( "{0}.{1}.{2}", BVParts[0], BVParts[1], BVParts[2] ); string[] 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() }; string JoinedLine = string.Join( "; ", RowProperties ); JoinedLine += "; "; CSVFile.WriteLine( JoinedLine ); } } CSVFile.Flush(); CSVFile.Close(); CSVFile = null; } List<FCSVRow> 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, }; } }
/// <summary> /// /// </summary> /// <returns></returns> public ActionResult Index( FormCollection Form ) { using( FAutoScopedLogTimer LogTimer = new FAutoScopedLogTimer( this.GetType().ToString(), bCreateNewLog: true ) ) { FormHelper FormData = new FormHelper( Request, Form, "JustReport" ); CSV_ViewModel Results = GetResults( FormData ); Results.GenerationTime = LogTimer.GetElapsedSeconds().ToString( "F2" ); return View( "Index", Results ); } }
/// <summary> /// Retrieve all Buggs matching the search criteria. /// </summary> /// <param name="FormData">The incoming form of search criteria from the client.</param> /// <param name="BuggIDToBeAddedToJira">ID of the bugg that will be added to JIRA</param> /// <returns>A view to display the filtered Buggs.</returns> public ReportsViewModel GetResults( FormHelper FormData, int BuggIDToBeAddedToJira ) { BuggRepository BuggsRepo = new BuggRepository(); CrashRepository CrashRepo = new CrashRepository(); // @TODO yrx 2015-02-17 BuggIDToBeAddedToJira replace with List<int> based on check box and Submit? // It would be great to have a CSV export of this as well with buggs ID being the key I can then use to join them :) // // Enumerate JIRA projects if needed. // https://jira.ol.epicgames.net//rest/api/2/project var JC = JiraConnection.Get(); var JiraComponents = JC.GetNameToComponents(); var JiraVersions = JC.GetNameToVersions(); using( FAutoScopedLogTimer LogTimer = new FAutoScopedLogTimer( this.GetType().ToString() ) ) { string AnonumousGroup = "Anonymous"; //List<String> Users = FRepository.Get().GetUserNamesFromGroupName( AnonumousGroup ); int AnonymousGroupID = FRepository.Get( BuggsRepo ).FindOrAddGroup( AnonumousGroup ); HashSet<int> AnonumousIDs = FRepository.Get( BuggsRepo ).GetUserIdsFromUserGroup( AnonumousGroup ); int AnonymousID = AnonumousIDs.First(); var Crashes = CrashRepo .FilterByDate( CrashRepo.ListAll(), FormData.DateFrom, FormData.DateTo ) // Only crashes and asserts .Where( Crash => Crash.CrashType == 1 || Crash.CrashType == 2 ) // Only anonymous user .Where( Crash => Crash.UserNameId.Value == AnonymousID ) .Select( Crash => new { ID = Crash.Id, TimeOfCrash = Crash.TimeOfCrash.Value, //UserID = Crash.UserNameId.Value, BuildVersion = Crash.BuildVersion, JIRA = Crash.Jira, Platform = Crash.PlatformName, FixCL = Crash.FixedChangeList, BuiltFromCL = Crash.BuiltFromCL, Pattern = Crash.Pattern, MachineID = Crash.MachineId, Branch = Crash.Branch, Description = Crash.Description, RawCallStack = Crash.RawCallStack, } ) .ToList(); int NumCrashes = Crashes.Count; /* // Build patterns for crashes where patters is null. var CrashesWithoutPattern = FRepository.Get().Crashes .FilterByDate( FRepository.Get().Crashes.ListAll(), FormData.DateFrom, FormData.DateTo.AddDays( 1 ) ) // Only crashes and asserts .Where( Crash => Crash.Pattern == null || Crash.Pattern == "" ) .Select( Crash => Crash ) .ToList(); foreach( var Crash in CrashesWithoutPattern ) { Crash.BuildPattern(); } */ // Total # of ALL (Anonymous) crashes in timeframe int TotalAnonymousCrashes = NumCrashes; // Total # of UNIQUE (Anonymous) crashes in timeframe HashSet<string> UniquePatterns = new HashSet<string>(); HashSet<string> UniqueMachines = new HashSet<string>(); Dictionary<string, int> PatternToCount = new Dictionary<string, int>(); //List<int> CrashesWithoutPattern = new List<int>(); //List<DateTime> CrashesWithoutPatternDT = new List<DateTime>(); foreach( var Crash in Crashes ) { if( string.IsNullOrEmpty( Crash.Pattern ) ) { //CrashesWithoutPattern.Add( Crash.ID ); //CrashesWithoutPatternDT.Add( Crash.TimeOfCrash ); continue; } UniquePatterns.Add( Crash.Pattern ); UniqueMachines.Add( Crash.MachineID ); bool bAdd = !PatternToCount.ContainsKey( Crash.Pattern ); if( bAdd ) { PatternToCount.Add( Crash.Pattern, 1 ); } else { PatternToCount[Crash.Pattern]++; } } var PatternToCountOrdered = PatternToCount.OrderByDescending( X => X.Value ).ToList(); const int NumTopRecords = 200; var PatternAndCount = PatternToCountOrdered.Take( NumTopRecords ).ToDictionary( X => X.Key, Y => Y.Value ); int TotalUniqueAnonymousCrashes = UniquePatterns.Count; // Total # of AFFECTED USERS (Anonymous) in timeframe int TotalAffectedUsers = UniqueMachines.Count; var RealBuggs = BuggsRepo.Context.Buggs.Where( Bugg => PatternAndCount.Keys.Contains( Bugg.Pattern ) ).ToList(); // Build search string. HashSet<string> FoundJiras = new HashSet<string>(); Dictionary<string, List<Bugg>> JiraIDtoBugg = new Dictionary<string, List<Bugg>>(); List<Bugg> Buggs = new List<Bugg>( NumTopRecords ); foreach( var Top in PatternAndCount ) { Bugg NewBugg = RealBuggs.Where( X => X.Pattern == Top.Key ).FirstOrDefault(); if( NewBugg != null ) { using( FAutoScopedLogTimer TopTimer = new FAutoScopedLogTimer( string.Format( "{0}:{1}", Buggs.Count + 1, NewBugg.Id ) ) ) { var CrashesForBugg = Crashes.Where( Crash => Crash.Pattern == Top.Key ).ToList(); // Convert anonymous to full type; var FullCrashesForBugg = new List<Crash>( CrashesForBugg.Count ); foreach( var Anon in CrashesForBugg ) { FullCrashesForBugg.Add( new Crash() { ID = Anon.ID, TimeOfCrash = Anon.TimeOfCrash, BuildVersion = Anon.BuildVersion, Jira = Anon.JIRA, Platform = Anon.Platform, FixCL = Anon.FixCL, BuiltFromCL = Anon.BuiltFromCL, Pattern = Anon.Pattern, MachineId = Anon.MachineID, Branch = Anon.Branch, Description = Anon.Description, RawCallStack = Anon.RawCallStack, } ); } NewBugg.PrepareBuggForJira( FullCrashesForBugg ); // Verify valid JiraID, this may be still a TTP if( !string.IsNullOrEmpty( NewBugg.Jira ) ) { int TTPID = 0; int.TryParse( NewBugg.Jira, out TTPID ); if( TTPID == 0 ) { AddBuggJiraMapping(NewBugg, ref FoundJiras, ref JiraIDtoBugg); } } Buggs.Add( NewBugg ); } } else { FLogger.Global.WriteEvent( "Bugg for pattern " + Top.Key + " not found" ); } } if( BuggIDToBeAddedToJira > 0 ) { var Bugg = Buggs.Where( X => X.Id == BuggIDToBeAddedToJira ).FirstOrDefault(); if( Bugg != null ) { Bugg.CopyToJira(); AddBuggJiraMapping( Bugg, ref FoundJiras, ref JiraIDtoBugg ); } } if( JC.CanBeUsed() ) { var BuggsCopy = new List<Bugg>( Buggs ); HashSet<string> InvalidJiras = new HashSet<string>(); // Simple verification of JIRA foreach (var Value in FoundJiras) { if( Value.Length < 3 || !Value.Contains('-') ) { InvalidJiras.Add(Value); } } foreach (var InvalidJira in InvalidJiras) { FoundJiras.Remove( InvalidJira ); } // Grab the data form JIRA. string JiraSearchQuery = string.Join( " OR ", FoundJiras ); using( FAutoScopedLogTimer JiraResultsTimer = new FAutoScopedLogTimer( "JiraResults" ) ) { var JiraResults = new Dictionary<string, Dictionary<string, object>>(); try { JiraResults = JC.SearchJiraTickets( JiraSearchQuery, new string[] { "key", // string "summary", // string "components", // System.Collections.ArrayList, Dictionary<string,object>, name "resolution", // System.Collections.Generic.Dictionary`2[System.String,System.Object], name "fixVersions", // System.Collections.ArrayList, Dictionary<string,object>, name "customfield_11200" // string } ); } catch (System.Exception) { } // Jira Key, Summary, Components, Resolution, Fix version, Fix changelist foreach( var Jira in JiraResults ) { string JiraID = Jira.Key; string Summary = (string)Jira.Value["summary"]; string ComponentsText = ""; System.Collections.ArrayList Components = (System.Collections.ArrayList)Jira.Value["components"]; foreach( Dictionary<string, object> Component in Components ) { ComponentsText += (string)Component["name"]; ComponentsText += " "; } Dictionary<string, object> ResolutionFields = (Dictionary<string, object>)Jira.Value["resolution"]; string Resolution = ResolutionFields != null ? (string)ResolutionFields["name"] : ""; string FixVersionsText = ""; System.Collections.ArrayList FixVersions = (System.Collections.ArrayList)Jira.Value["fixVersions"]; foreach( Dictionary<string, object> FixVersion in FixVersions ) { FixVersionsText += (string)FixVersion["name"]; FixVersionsText += " "; } int FixCL = Jira.Value["customfield_11200"] != null ? (int)(decimal)Jira.Value["customfield_11200"] : 0; List<Bugg> BuggsForJira; JiraIDtoBugg.TryGetValue( JiraID, out BuggsForJira ); //var BuggsForJira = JiraIDtoBugg[JiraID]; if( BuggsForJira != null ) { foreach( Bugg Bugg in BuggsForJira ) { Bugg.JiraSummary = Summary; Bugg.JiraComponentsText = ComponentsText; Bugg.JiraResolution = Resolution; Bugg.JiraFixVersionsText = FixVersionsText; if( FixCL != 0 ) { Bugg.JiraFixCL = FixCL.ToString(); } BuggsCopy.Remove( Bugg ); } } } } // If there are buggs, we need to update the summary to indicate an error. // Usually caused when bugg's project has changed. foreach( var Bugg in BuggsCopy.Where( b => !string.IsNullOrEmpty( b.Jira ) ) ) { Bugg.JiraSummary = "JIRA MISMATCH"; Bugg.JiraComponentsText = "JIRA MISMATCH"; Bugg.JiraResolution = "JIRA MISMATCH"; Bugg.JiraFixVersionsText = "JIRA MISMATCH"; Bugg.JiraFixCL = "JIRA MISMATCH"; } } Buggs = Buggs.OrderByDescending( b => b.CrashesInTimeFrameGroup ).ToList(); return new ReportsViewModel { Buggs = Buggs, /*Crashes = Crashes,*/ DateFrom = (long)( FormData.DateFrom - CrashesViewModel.Epoch ).TotalMilliseconds, DateTo = (long)( FormData.DateTo - CrashesViewModel.Epoch ).TotalMilliseconds, TotalAffectedUsers = TotalAffectedUsers, TotalAnonymousCrashes = TotalAnonymousCrashes, TotalUniqueAnonymousCrashes = TotalUniqueAnonymousCrashes }; } }
/// <summary> /// /// </summary> /// <param name="ReportsForm"></param> /// <returns></returns> public ActionResult Index( FormCollection ReportsForm ) { using( FAutoScopedLogTimer LogTimer = new FAutoScopedLogTimer( this.GetType().ToString(), bCreateNewLog: true ) ) { FormHelper FormData = new FormHelper( Request, ReportsForm, "JustReport" ); // Handle 'CopyToJira' button int BuggIDToBeAddedToJira = 0; foreach( var Entry in ReportsForm ) { if( Entry.ToString().Contains( Bugg.JiraSubmitName ) ) { int.TryParse( Entry.ToString().Substring( Bugg.JiraSubmitName.Length ), out BuggIDToBeAddedToJira ); break; } } ReportsViewModel Results = GetResults( FormData, BuggIDToBeAddedToJira ); Results.GenerationTime = LogTimer.GetElapsedSeconds().ToString( "F2" ); return View( "Index", Results ); } }
/// <summary> /// The Index action. /// </summary> /// <param name="BuggsForm">The form of user data passed up from the client.</param> /// <returns>The view to display a list of Buggs on the client.</returns> public ActionResult Index( FormCollection BuggsForm ) { FormHelper FormData = new FormHelper( Request, BuggsForm, "CrashesInTimeFrame" ); BuggsViewModel Results = LocalBuggRepository.GetResults( FormData ); return View( "Index", Results ); }
/// <summary> /// Return a view model containing a sorted list of crashes based on the user input. /// </summary> /// <param name="FormData">The user input from the client.</param> /// <returns>A view model to use to display a list of crashes.</returns> /// <remarks>The filtering mechanism works by generating a fluent query to describe the set of crashes we wish to view. Basically, 'Select.Where().Where().Where().Where()' etc. /// The filtering is applied in the following order: /// 1. Get all crashes. /// 2. Filter between the start and end dates. /// 3. Apply the search query. /// 4. Filter by the branch. /// 5. Filter by the game name. /// 6. Filter by the type of reports to view. /// 7. Filter by the user group. /// 8. Sort the results by the sort term. /// 9. Take one page of results.</remarks> public CrashesViewModel GetResults(FormHelper FormData) { using (FAutoScopedLogTimer LogTimer = new FAutoScopedLogTimer(this.GetType().ToString())) { UsersMapping UniqueUser = null; IQueryable <Crash> Results = null; int Skip = (FormData.Page - 1) * FormData.PageSize; int Take = FormData.PageSize; Results = ListAll(); Results = FilterByDate(Results, FormData.DateFrom, FormData.DateTo); // Grab Results if (!string.IsNullOrEmpty(FormData.SearchQuery)) { string DecodedQuery = HttpUtility.HtmlDecode(FormData.SearchQuery).ToLower(); using (FScopedLogTimer LogTimer2 = new FScopedLogTimer("CrashRepository.GetResults.FindUserFromQuery" + "(Query=" + DecodedQuery + ")")) { if (!string.IsNullOrEmpty(DecodedQuery)) { // Check if we are looking for user name. string[] Params = DecodedQuery.Split(new string[] { "user:"******"SELECT * FROM [analyticsdb-01.dmz.epicgames.net].[CrashReport].[dbo].[UsersMapping] WHERE lower(UserName) = {0} OR lower(UserEmail) = {0}", Params[1]); foreach (UsersMapping TheUser in FoundUsers) { UniqueUser = TheUser; break; } if (UniqueUser != null) { Results = Results.Where(CrashInstance => CrashInstance.EpicAccountId == UniqueUser.EpicAccountId); } else { Results = Results.Where(CrashInstance => CrashInstance.EpicAccountId == "SomeValueThatIsNotPresentInTheDatabase"); } } else { Results = Search(Results, DecodedQuery); } } } } // Start Filtering the results // Filter by BranchName if (!string.IsNullOrEmpty(FormData.BranchName)) { if (FormData.BranchName.StartsWith("-")) { Results = ( from CrashDetail in Results where !CrashDetail.Branch.Contains(FormData.BranchName.Substring(1)) select CrashDetail ); } else { Results = ( from CrashDetail in Results where CrashDetail.Branch.Contains(FormData.BranchName) select CrashDetail ); } } // Filter by GameName if (!string.IsNullOrEmpty(FormData.GameName)) { if (FormData.GameName.StartsWith("-")) { Results = ( from CrashDetail in Results where !CrashDetail.GameName.Contains(FormData.GameName.Substring(1)) select CrashDetail ); } else { Results = ( from CrashDetail in Results where CrashDetail.GameName.Contains(FormData.GameName) select CrashDetail ); } } // Filter by Crash Type if (FormData.CrashType != "All") { switch (FormData.CrashType) { case "Crashes": Results = Results.Where(CrashInstance => CrashInstance.CrashType == 1); break; case "Assert": Results = Results.Where(CrashInstance => CrashInstance.CrashType == 2); break; case "Ensure": Results = Results.Where(CrashInstance => CrashInstance.CrashType == 3); break; case "CrashesAsserts": Results = Results.Where(CrashInstance => CrashInstance.CrashType == 1 || CrashInstance.CrashType == 2); break; } } // Get UserGroup ResultCounts Dictionary <string, int> GroupCounts = GetCountsByGroupFromCrashes(Results); // Filter by user group if present int UserGroupId; if (!string.IsNullOrEmpty(FormData.UserGroup)) { UserGroupId = FindOrAddUserGroup(FormData.UserGroup); } else { UserGroupId = 1; } Results = ( from CrashDetail in Results from UserDetail in Context.Users where UserDetail.UserGroupId == UserGroupId && (CrashDetail.UserNameId == UserDetail.Id || CrashDetail.UserName == UserDetail.UserName) select CrashDetail ); // Pass in the results and return them sorted properly Results = GetSortedResults(Results, FormData.SortTerm, (FormData.SortOrder == "Descending")); // Get the Count for pagination int ResultCount = Results.Count(); // Grab just the results we want to display on this page Results = Results.Skip(Skip).Take(Take); using (FScopedLogTimer LogTimer3 = new FScopedLogTimer("CrashRepository.GetResults.GetCallstacks")) { // Process call stack for display foreach (Crash CrashInstance in Results) { // Put callstacks into an list so we can access them line by line in the view CrashInstance.CallStackContainer = GetCallStack(CrashInstance); } } return(new CrashesViewModel { Results = Results, PagingInfo = new PagingInfo { CurrentPage = FormData.Page, PageSize = FormData.PageSize, TotalResults = ResultCount }, SortOrder = FormData.SortOrder, SortTerm = FormData.SortTerm, UserGroup = FormData.UserGroup, CrashType = FormData.CrashType, SearchQuery = FormData.SearchQuery, DateFrom = (long)(FormData.DateFrom - CrashesViewModel.Epoch).TotalMilliseconds, DateTo = (long)(FormData.DateTo - CrashesViewModel.Epoch).TotalMilliseconds, BranchName = FormData.BranchName, GameName = FormData.GameName, GroupCounts = GroupCounts, RealUserName = UniqueUser != null?UniqueUser.ToString() : null, }); } }
private IQueryable <Crash> ConstructQuery(FormHelper FormData) { var results = ListAll(); // Grab Results if (!string.IsNullOrEmpty(FormData.SearchQuery)) { if (!string.IsNullOrEmpty(FormData.UsernameQuery)) { //We only use SearchQuery now for CallStack searching - if there's a searchquery value and a Username value, we need to get rid of the //Username so that we can create a broader search range FormData.UsernameQuery = ""; } string DecodedQuery = HttpUtility.HtmlDecode(FormData.SearchQuery).ToLower(); using (FScopedLogTimer LogTimer2 = new FScopedLogTimer("CrashRepository.GetResults.FindUserFromQuery" + "(Query=" + DecodedQuery + ")")) { results = results.Where(item => item.RawCallStack.Contains(FormData.SearchQuery)); } } // Filter by Crash Type if (FormData.CrashType != "All") { switch (FormData.CrashType) { case "Crashes": results = results.Where(CrashInstance => CrashInstance.CrashType == 1); break; case "Assert": results = results.Where(CrashInstance => CrashInstance.CrashType == 2); break; case "Ensure": results = results.Where(CrashInstance => CrashInstance.CrashType == 3); break; case "CrashesAsserts": results = results.Where(CrashInstance => CrashInstance.CrashType == 1 || CrashInstance.CrashType == 2); break; } } if (!string.IsNullOrEmpty(FormData.UsernameQuery)) { results = ( from CrashDetail in results where CrashDetail.UserName.Equals(FormData.UsernameQuery) select CrashDetail ); } // Start Filtering the results if (!string.IsNullOrEmpty(FormData.EpicIdQuery)) { var DecodedQuery = HttpUtility.HtmlDecode(FormData.EpicIdQuery).ToLower(); results = ( from CrashDetail in results where CrashDetail.EpicAccountId.Equals(FormData.EpicIdQuery) select CrashDetail ); } if (!string.IsNullOrEmpty(FormData.MachineIdQuery)) { var DecodedQuery = HttpUtility.HtmlDecode(FormData.MachineIdQuery).ToLower(); results = ( from CrashDetail in results where CrashDetail.ComputerName.Equals(FormData.MachineIdQuery) select CrashDetail ); } if (!string.IsNullOrEmpty(FormData.JiraQuery)) { var DecodedQuery = HttpUtility.HtmlDecode(FormData.JiraQuery).ToLower(); results = ( from CrashDetail in results where CrashDetail.TTPID.Equals(FormData.JiraQuery) select CrashDetail ); } // Filter by BranchName if (!string.IsNullOrEmpty(FormData.BranchName)) { if (FormData.BranchName.StartsWith("-")) { results = ( from CrashDetail in results where !CrashDetail.Branch.Contains(FormData.BranchName.Substring(1)) select CrashDetail ); } else { results = ( from CrashDetail in results where CrashDetail.Branch.Equals(FormData.BranchName) select CrashDetail ); } } else { results = ( from CrashDetail in results where !CrashDetail.Branch.Equals("UE4-UT-Releases") && !CrashDetail.Branch.Equals("UE4-UT") select CrashDetail ); } // Filter by GameName if (!string.IsNullOrEmpty(FormData.GameName)) { if (FormData.GameName.StartsWith("-")) { results = ( from CrashDetail in results where !CrashDetail.GameName.Contains(FormData.GameName.Substring(1)) select CrashDetail ); } else { results = ( from CrashDetail in results where CrashDetail.GameName.Contains(FormData.GameName) select CrashDetail ); } } if (!string.IsNullOrEmpty(FormData.MessageQuery)) { results = ( from CrashDetail in results where SqlMethods.Like(CrashDetail.Summary, "%" + FormData.MessageQuery + "%") select CrashDetail ); } if (!string.IsNullOrEmpty(FormData.DescriptionQuery)) { results = ( from CrashDetail in results where SqlMethods.Like(CrashDetail.Description, "%" + FormData.DescriptionQuery + "%") select CrashDetail ); } if (!string.IsNullOrEmpty(FormData.SearchQuery)) { results = ( from CrashDetail in results where SqlMethods.Like(CrashDetail.RawCallStack, "%" + FormData.SearchQuery + "%") select CrashDetail ); } return(results); }
/// <summary> /// Return a view model containing a sorted list of crashes based on the user input. /// </summary> /// <param name="FormData">The user input from the client.</param> /// <returns>A view model to use to display a list of crashes.</returns> /// <remarks>The filtering mechanism works by generating a fluent query to describe the set of crashes we wish to view. Basically, 'Select.Where().Where().Where().Where()' etc. /// The filtering is applied in the following order: /// 1. Get all crashes. /// 2. Filter between the start and end dates. /// 3. Apply the search query. /// 4. Filter by the branch. /// 5. Filter by the game name. /// 6. Filter by the type of reports to view. /// 7. Filter by the user group. /// 8. Sort the results by the sort term. /// 9. Take one page of results.</remarks> public CrashesViewModel GetResults(FormHelper FormData) { using (FAutoScopedLogTimer LogTimer = new FAutoScopedLogTimer(this.GetType().ToString())) { UsersMapping UniqueUser = null; IEnumerable <Crash> Results = null; int Skip = (FormData.Page - 1) * FormData.PageSize; int Take = FormData.PageSize; var ResultsAll = ConstructQuery(FormData); // Filter by data and get as enumerable. Results = FilterByDate(ResultsAll, FormData.DateFrom, FormData.DateTo); // Get UserGroup ResultCounts Dictionary <string, int> GroupCounts = GetCountsByGroupFromCrashes(Results); // Filter by user group if present int UserGroupId; if (!string.IsNullOrEmpty(FormData.UserGroup)) { UserGroupId = FRepository.Get(this).FindOrAddGroup(FormData.UserGroup); } else { UserGroupId = 1; } HashSet <int> UserIdsForGroup = FRepository.Get(this).GetUserIdsFromUserGroupId(UserGroupId); using (FScopedLogTimer LogTimer3 = new FScopedLogTimer("CrashRepository.Results.Users")) { List <Crash> NewResults = new List <Crash>(); foreach (Crash Crash in Results) { if (UserIdsForGroup.Contains(Crash.UserNameId.Value)) { NewResults.Add(Crash); } } Results = NewResults; } // Pass in the results and return them sorted properly Results = GetSortedResults(Results, FormData.SortTerm, (FormData.SortOrder == "Descending")); // Get the Count for pagination int ResultCount = 0; using (FScopedLogTimer LogTimer3 = new FScopedLogTimer("CrashRepository.Results.Users")) { ResultCount = Results.Count(); } // Grab just the results we want to display on this page Results = Results.Skip(Skip).Take(Take); using (FScopedLogTimer LogTimer3 = new FScopedLogTimer("CrashRepository.GetResults.GetCallstacks")) { // Process call stack for display foreach (Crash CrashInstance in Results) { // Put callstacks into an list so we can access them line by line in the view CrashInstance.CallStackContainer = GetCallStack(CrashInstance); } } return(new CrashesViewModel { Results = Results, PagingInfo = new PagingInfo { CurrentPage = FormData.Page, PageSize = FormData.PageSize, TotalResults = ResultCount }, SortOrder = FormData.SortOrder, SortTerm = FormData.SortTerm, UserGroup = FormData.UserGroup, CrashType = FormData.CrashType, SearchQuery = FormData.SearchQuery, UsernameQuery = FormData.UsernameQuery, EpicIdQuery = FormData.EpicIdQuery, MachineIdQuery = FormData.MachineIdQuery, DescriptionQuery = FormData.DescriptionQuery, MessageQuery = FormData.MessageQuery, JiraQuery = FormData.JiraQuery, DateFrom = (long)(FormData.DateFrom - CrashesViewModel.Epoch).TotalMilliseconds, DateTo = (long)(FormData.DateTo - CrashesViewModel.Epoch).TotalMilliseconds, BranchName = FormData.BranchName, GameName = FormData.GameName, GroupCounts = GroupCounts, RealUserName = UniqueUser != null?UniqueUser.ToString() : null, }); } }
/// <summary>Constructs query for filtering.</summary> private IQueryable <Crash> ConstructQueryForFiltering(FormHelper FormData) { var Results = ListAll(); // Grab Results string QueryString = HttpUtility.HtmlDecode(FormData.SearchQuery); if (!string.IsNullOrEmpty(QueryString)) { if (!string.IsNullOrEmpty(QueryString)) { //We only use SearchQuery now for CallStack searching - if there's a SearchQuery value and a Username value, we need to get rid of the //Username so that we can create a broader search range FormData.UsernameQuery = ""; } Results = ( from CrashDetail in Results where SqlMethods.Like(CrashDetail.RawCallStack, "%" + FormData.SearchQuery + "%") select CrashDetail ); } // Filter by Crash Type if (FormData.CrashType != "All") { switch (FormData.CrashType) { case "Crashes": Results = Results.Where(CrashInstance => CrashInstance.CrashType == 1); break; case "Assert": Results = Results.Where(CrashInstance => CrashInstance.CrashType == 2); break; case "Ensure": Results = Results.Where(CrashInstance => CrashInstance.CrashType == 3); break; case "CrashesAsserts": Results = Results.Where(CrashInstance => CrashInstance.CrashType == 1 || CrashInstance.CrashType == 2); break; } } // JRX Restore EpicID/UserName searching if (!string.IsNullOrEmpty(FormData.UsernameQuery)) { var DecodedUsername = HttpUtility.HtmlDecode(FormData.UsernameQuery).ToLower(); Results = ( from CrashDetail in Results where CrashDetail.UserName.Equals(DecodedUsername) select CrashDetail ); } // Start Filtering the results if (!string.IsNullOrEmpty(FormData.EpicIdOrMachineQuery)) { var DecodedEpicOrMachineID = HttpUtility.HtmlDecode(FormData.EpicIdOrMachineQuery).ToLower(); Results = ( from CrashDetail in Results where CrashDetail.EpicAccountId.Equals(DecodedEpicOrMachineID) || CrashDetail.MachineId.Equals(DecodedEpicOrMachineID) select CrashDetail ); } if (!string.IsNullOrEmpty(FormData.JiraQuery)) { var DecodedJIRA = HttpUtility.HtmlDecode(FormData.JiraQuery).ToLower(); Results = ( from CrashDetail in Results where CrashDetail.Jira.Contains(DecodedJIRA) select CrashDetail ); } // Filter by BranchName if (!string.IsNullOrEmpty(FormData.BranchName)) { Results = ( from CrashDetail in Results where CrashDetail.Branch.Equals(FormData.BranchName) select CrashDetail ); } else { Results = ( from CrashDetail in Results where !CrashDetail.Branch.Contains("UE4-UT") select CrashDetail ); } // Filter by VersionName if (!string.IsNullOrEmpty(FormData.VersionName)) { Results = ( from CrashDetail in Results where CrashDetail.BuildVersion.Equals(FormData.VersionName) select CrashDetail ); } // Filter by GameName if (!string.IsNullOrEmpty(FormData.GameName)) { var DecodedGameName = HttpUtility.HtmlDecode(FormData.GameName).ToLower(); if (DecodedGameName.StartsWith("-")) { Results = ( from CrashDetail in Results where !CrashDetail.GameName.Contains(DecodedGameName.Substring(1)) select CrashDetail ); } else { Results = ( from CrashDetail in Results where CrashDetail.GameName.Contains(DecodedGameName) select CrashDetail ); } } if (!string.IsNullOrEmpty(FormData.MessageQuery)) { Results = ( from CrashDetail in Results where SqlMethods.Like(CrashDetail.Summary, "%" + FormData.MessageQuery + "%") || SqlMethods.Like(CrashDetail.Description, "%" + FormData.MessageQuery + "%") select CrashDetail ); } return(Results); }
/// <summary> /// Retrieve all Buggs matching the search criteria. /// </summary> /// <param name="FormData">The incoming form of search criteria from the client.</param> /// <param name="BuggIDToBeAddedToJira">ID of the bugg that will be added to JIRA</param> /// <returns>A view to display the filtered Buggs.</returns> public ReportsViewModel GetResults( FormHelper FormData, int BuggIDToBeAddedToJira ) { // @TODO yrx 2015-02-17 BuggIDToBeAddedToJira replace with List<int> based on check box and Submit? // Enumerate JIRA projects if needed. // https://jira.ol.epicgames.net//rest/api/2/project var JC = JiraConnection.Get(); var JiraComponents = JC.GetNameToComponents(); var JiraVersions = JC.GetNameToVersions(); using( FAutoScopedLogTimer LogTimer = new FAutoScopedLogTimer( this.GetType().ToString() ) ) { string AnonumousGroup = "Anonymous"; List<String> Users = CrashRepository.GetUsersForGroup( AnonumousGroup ); int AnonymousGroupID = BuggRepository.GetIdFromUserGroup( AnonumousGroup ); HashSet<int> AnonumousIDs = BuggRepository.GetUserIdsFromUserGroup( AnonumousGroup ); int AnonymousID = AnonumousIDs.First(); HashSet<string> UserNamesForUserGroup = BuggRepository.GetUserNamesFromUserGroups( AnonumousGroup ); //FormData.DateFrom = DateTime.Now.AddDays( -1 ); var Crashes = CrashRepository .FilterByDate( CrashRepository.ListAll(), FormData.DateFrom, FormData.DateTo.AddDays( 1 ) ) // Only crashes and asserts .Where( Crash => Crash.CrashType == 1 || Crash.CrashType == 2 ) // Only anonymous user .Where( Crash => Crash.UserNameId.Value == AnonymousID ) .Select( Crash => new { ID = Crash.Id, TimeOfCrash = Crash.TimeOfCrash.Value, //UserID = Crash.UserNameId.Value, BuildVersion = Crash.BuildVersion, JIRA = Crash.TTPID, Platform = Crash.PlatformName, FixCL = Crash.FixedChangeList, BuiltFromCL = Crash.ChangeListVersion, Pattern = Crash.Pattern, MachineID = Crash.ComputerName, Branch = Crash.Branch, } ) .ToList(); int NumCrashes = Crashes.Count; /* // Build patterns for crashes where patters is null. var CrashesWithoutPattern = CrashRepository .FilterByDate( CrashRepository.ListAll(), FormData.DateFrom, FormData.DateTo.AddDays( 1 ) ) // Only crashes and asserts .Where( Crash => Crash.Pattern == null || Crash.Pattern == "" ) .Select( Crash => Crash ) .ToList(); foreach( var Crash in CrashesWithoutPattern ) { ////BuildPattern( Crash ); } */ // Total # of ALL (Anonymous) crashes in timeframe int TotalAnonymousCrashes = NumCrashes; // Total # of UNIQUE (Anonymous) crashes in timeframe HashSet<string> UniquePatterns = new HashSet<string>(); HashSet<string> UniqueMachines = new HashSet<string>(); Dictionary<string, int> PatternToCount = new Dictionary<string, int>(); //List<int> CrashesWithoutPattern = new List<int>(); //List<DateTime> CrashesWithoutPatternDT = new List<DateTime>(); foreach( var Crash in Crashes ) { if( string.IsNullOrEmpty( Crash.Pattern ) ) { //CrashesWithoutPattern.Add( Crash.ID ); //CrashesWithoutPatternDT.Add( Crash.TimeOfCrash ); continue; } UniquePatterns.Add( Crash.Pattern ); UniqueMachines.Add( Crash.MachineID ); bool bAdd = !PatternToCount.ContainsKey( Crash.Pattern ); if( bAdd ) { PatternToCount.Add( Crash.Pattern, 1 ); } else { PatternToCount[Crash.Pattern]++; } } var PatternToCountOrdered = PatternToCount.OrderByDescending( X => X.Value ).ToList(); // The 100 top. var PatternAndCount = PatternToCountOrdered.Take( 100 ).ToDictionary( X => X.Key, Y => Y.Value ); int TotalUniqueAnonymousCrashes = UniquePatterns.Count; // Total # of AFFECTED USERS (Anonymous) in timeframe int TotalAffectedUsers = UniqueMachines.Count; var RealBuggs = BuggRepository.GetDataContext().Buggs.Where( Bugg => PatternAndCount.Keys.Contains( Bugg.Pattern ) ).ToList(); // Build search string. List<string> FoundJiras = new List<string>(); Dictionary<string, List<Bugg>> JiraIDtoBugg = new Dictionary<string, List<Bugg>>(); List<Bugg> Buggs = new List<Bugg>( 100 ); foreach( var Top in PatternAndCount ) { Bugg NewBugg = new Bugg(); Bugg RealBugg = RealBuggs.Where( X => X.Pattern == Top.Key ).FirstOrDefault(); if( RealBugg != null ) { using( FAutoScopedLogTimer TopTimer = new FAutoScopedLogTimer( string.Format("{0}:{1}", Buggs.Count+1, RealBugg.Id ) ) ) { // Index // number NewBugg.Id = RealBugg.Id; // CrashGroup URL (a link to the Bugg) NewBugg.TTPID = RealBugg.TTPID; // JIRA NewBugg.FixedChangeList = RealBugg.FixedChangeList; // FixCL NewBugg.NumberOfCrashes = Top.Value; // # Occurrences NewBugg.Pattern = RealBugg.Pattern; // # Occurrences //NewBugg.BuildVersion = var CrashesForBugg = Crashes.Where( Crash => Crash.Pattern == Top.Key ).ToList(); NewBugg.AffectedVersions = new SortedSet<string>(); NewBugg.AffectedMajorVersions = new SortedSet<string>(); // 4.4, 4.5 and so NewBugg.BranchesFoundIn = new SortedSet<string>(); NewBugg.AffectedPlatforms = new SortedSet<string>(); HashSet<string> MachineIds = new HashSet<string>(); int FirstCLAffected = int.MaxValue; foreach( var Crash in CrashesForBugg ) { // Only add machine if the number has 32 characters if( Crash.MachineID != null && Crash.MachineID.Length == 32 ) { MachineIds.Add( Crash.MachineID ); } // Ignore bad build versions. // @TODO yrx 2015-02-17 What about launcher? if( Crash.BuildVersion.StartsWith( "4." ) ) { if( !string.IsNullOrEmpty(Crash.BuildVersion) ) { NewBugg.AffectedVersions.Add( Crash.BuildVersion ); } if( !string.IsNullOrEmpty( Crash.Branch ) && Crash.Branch.StartsWith( "UE4" ) ) { NewBugg.BranchesFoundIn.Add( Crash.Branch ); } int CrashBuiltFromCL = 0; int.TryParse( Crash.BuiltFromCL, out CrashBuiltFromCL ); if( CrashBuiltFromCL > 0 ) { FirstCLAffected = Math.Min( FirstCLAffected, CrashBuiltFromCL ); } if( !string.IsNullOrEmpty( Crash.Platform ) ) { // Platform = "Platform [Desc]"; var PlatSubs = Crash.Platform.Split( " ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries ); if( PlatSubs.Length >= 1 ) { NewBugg.AffectedPlatforms.Add( PlatSubs[0] ); } } } } // CopyToJira NewBugg.ToJiraFirstCLAffected = FirstCLAffected; if( NewBugg.AffectedVersions.Count > 0 ) { NewBugg.BuildVersion = NewBugg.AffectedVersions.Last(); // Latest Version Affected } foreach( var AffectedBuild in NewBugg.AffectedVersions ) { var Subs = AffectedBuild.Split( ".".ToCharArray(), StringSplitOptions.RemoveEmptyEntries ); if( Subs.Length >= 2 ) { string MajorVersion = Subs[0] + "." + Subs[1]; NewBugg.AffectedMajorVersions.Add( MajorVersion ); } } NewBugg.NumberOfUniqueMachines = MachineIds.Count; // # Affected Users string LatestCLAffected = CrashesForBugg. // CL of the latest build Where( Crash => Crash.BuildVersion == NewBugg.BuildVersion ). Max( Crash => Crash.BuiltFromCL ); int ILatestCLAffected = -1; int.TryParse( LatestCLAffected, out ILatestCLAffected ); NewBugg.LatestCLAffected = ILatestCLAffected; // Latest CL Affected string LatestOSAffected = CrashesForBugg.OrderByDescending( Crash => Crash.TimeOfCrash ).First().Platform; NewBugg.LatestOSAffected = LatestOSAffected; // Latest Environment Affected NewBugg.TimeOfFirstCrash = RealBugg.TimeOfFirstCrash; // First Crash Timestamp // ToJiraSummary var Callstack = RealBugg.GetFunctionCalls(); NewBugg.ToJiraSummary = Callstack.Count > 1 ? Callstack[0] : "No valid callstack found"; // ToJiraVersions NewBugg.ToJiraVersions = new List<object>(); foreach( var Version in NewBugg.AffectedMajorVersions ) { bool bValid = JC.GetNameToVersions().ContainsKey( Version ); if( bValid ) { NewBugg.ToJiraVersions.Add( JC.GetNameToVersions()[Version] ); } } // ToJiraBranches NewBugg.ToJiraBranches = new List<object>(); foreach( var Platform in NewBugg.BranchesFoundIn ) { string CleanedBranch = Platform.Contains( "UE4-Releases" ) ? "UE4-Releases" : Platform; Dictionary<string, object> JiraBranch = null; JC.GetNameToBranchFoundIn().TryGetValue( CleanedBranch, out JiraBranch ); if( JiraBranch != null && !NewBugg.ToJiraBranches.Contains( JiraBranch ) ) { NewBugg.ToJiraBranches.Add( JiraBranch ); } } // ToJiraPlatforms NewBugg.ToJiraPlatforms = new List<object>(); foreach( var Platform in NewBugg.AffectedPlatforms ) { bool bValid = JC.GetNameToPlatform().ContainsKey( Platform ); if( bValid ) { NewBugg.ToJiraPlatforms.Add( JC.GetNameToPlatform()[Platform] ); } } // Verify valid JiraID, this may be still a TTP if( !string.IsNullOrEmpty( NewBugg.TTPID ) ) { int TTPID = 0; int.TryParse( NewBugg.TTPID, out TTPID ); if( TTPID == 0 ) { AddBuggJiraMapping(NewBugg, ref FoundJiras, ref JiraIDtoBugg); } } Buggs.Add( NewBugg ); } } else { FLogger.WriteEvent( "Bugg for pattern " + Top.Key + " not found" ); } } if( BuggIDToBeAddedToJira > 0 ) { var Bugg = Buggs.Where( X => X.Id == BuggIDToBeAddedToJira ).FirstOrDefault(); if( Bugg != null ) { Bugg.CopyToJira(); AddBuggJiraMapping( Bugg, ref FoundJiras, ref JiraIDtoBugg ); } } if( JC.CanBeUsed() ) { // Grab the data form JIRA. string JiraSearchQuery = string.Join( " OR ", FoundJiras ); using( FAutoScopedLogTimer JiraResultsTimer = new FAutoScopedLogTimer( "JiraResults" ) ) { var JiraResults = JC.SearchJiraTickets( JiraSearchQuery, new string[] { "key", // string "summary", // string "components", // System.Collections.ArrayList, Dictionary<string,object>, name "resolution", // System.Collections.Generic.Dictionary`2[System.String,System.Object], name "fixVersions", // System.Collections.ArrayList, Dictionary<string,object>, name "customfield_11200" // string } ); // Jira Key, Summary, Components, Resolution, Fix version, Fix changelist foreach( var Jira in JiraResults ) { string JiraID = Jira.Key; string Summary = (string)Jira.Value["summary"]; string ComponentsText = ""; System.Collections.ArrayList Components = (System.Collections.ArrayList)Jira.Value["components"]; foreach( Dictionary<string, object> Component in Components ) { ComponentsText += (string)Component["name"]; ComponentsText += " "; } Dictionary<string, object> ResolutionFields = (Dictionary<string, object>)Jira.Value["resolution"]; string Resolution = ResolutionFields != null ? (string)ResolutionFields["name"] : ""; string FixVersionsText = ""; System.Collections.ArrayList FixVersions = (System.Collections.ArrayList)Jira.Value["fixVersions"]; foreach( Dictionary<string, object> FixVersion in FixVersions ) { FixVersionsText += (string)FixVersion["name"]; FixVersionsText += " "; } int FixCL = Jira.Value["customfield_11200"] != null ? (int)(decimal)Jira.Value["customfield_11200"] : 0; var BuggsForJira = JiraIDtoBugg[JiraID]; foreach( Bugg Bugg in BuggsForJira ) { Bugg.JiraSummary = Summary; Bugg.JiraComponentsText = ComponentsText; Bugg.JiraResolution = Resolution; Bugg.JiraFixVersionsText = FixVersionsText; if( FixCL != 0 ) { Bugg.JiraFixCL = FixCL.ToString(); } } } } } return new ReportsViewModel { Buggs = Buggs, /*Crashes = Crashes,*/ DateFrom = (long)( FormData.DateFrom - CrashesViewModel.Epoch ).TotalMilliseconds, DateTo = (long)( FormData.DateTo - CrashesViewModel.Epoch ).TotalMilliseconds, TotalAffectedUsers = TotalAffectedUsers, TotalAnonymousCrashes = TotalAnonymousCrashes, TotalUniqueAnonymousCrashes = TotalUniqueAnonymousCrashes }; } }
/// <summary> /// Retrieve all Buggs matching the search criteria. /// </summary> /// <param name="FormData">The incoming form of search criteria from the client.</param> /// <returns>A view to display the filtered Buggs.</returns> public BuggsViewModel GetResults(FormHelper FormData) { using (FAutoScopedLogTimer LogTimer = new FAutoScopedLogTimer(this.GetType().ToString())) { // Right now we take a Result IQueryable starting with ListAll() Buggs then we widdle away the result set by tacking on // Linq queries. Essentially it's Results.ListAll().Where().Where().Where().Where().Where().Where() // Could possibly create more optimized queries when we know exactly what we're querying // The downside is that if we add more parameters each query may need to be updated.... Or we just add new case statements // The other downside is that there's less code reuse, but that may be worth it. IEnumerable <Bugg> Results = null; int Skip = (FormData.Page - 1) * FormData.PageSize; int Take = FormData.PageSize; // Get every Bugg. var ResultsAll = ListAll(); // Look at all Buggs that are still 'open' i.e. the last crash occurred in our date range. Results = FilterByDate(ResultsAll, FormData.DateFrom, FormData.DateTo); // Filter results by build version. Results = FilterByBuildVersion(Results, FormData.BuildVersion); // Run at the end if (!string.IsNullOrEmpty(FormData.SearchQuery)) { Results = Search(Results, FormData.SearchQuery); } // Filter by Crash Type if (FormData.CrashType != "All") { switch (FormData.CrashType) { case "Crashes": Results = Results.Where(BuggInstance => BuggInstance.CrashType == 1); break; case "Assert": Results = Results.Where(BuggInstance => BuggInstance.CrashType == 2); break; case "Ensure": Results = Results.Where(BuggInstance => BuggInstance.CrashType == 3); break; case "CrashesAsserts": Results = Results.Where(BuggInstance => BuggInstance.CrashType == 1 || BuggInstance.CrashType == 2); break; } } // Get UserGroup ResultCounts SortedDictionary <string, int> GroupCounts = GetCountsByGroup(Results); // Filter by user group if present Results = FilterByUserGroup(Results, FormData.UserGroup); // Pass in the results and return them sorted properly IEnumerable <Bugg> SortedResults = GetSortedResults(Results, FormData.SortTerm, (FormData.SortOrder == "Descending"), FormData.DateFrom, FormData.DateTo, FormData.UserGroup); // Grab just the results we want to display on this page var SortedResultsList = SortedResults.ToList(); var TotalCountedRecords = SortedResultsList.Count(); SortedResultsList = SortedResultsList.GetRange(Skip, TotalCountedRecords >= Skip + Take ? Take : TotalCountedRecords); return(new BuggsViewModel { Results = SortedResultsList, PagingInfo = new PagingInfo { CurrentPage = FormData.Page, PageSize = FormData.PageSize, TotalResults = Results.Count() }, SortTerm = FormData.SortTerm, SortOrder = FormData.SortOrder, UserGroup = FormData.UserGroup, CrashType = FormData.CrashType, SearchQuery = FormData.SearchQuery, DateFrom = (long)(FormData.DateFrom - CrashesViewModel.Epoch).TotalMilliseconds, DateTo = (long)(FormData.DateTo - CrashesViewModel.Epoch).TotalMilliseconds, BuildVersion = FormData.BuildVersion, GroupCounts = GroupCounts, }); } }
/// <summary> /// Return a view model containing a sorted list of crashes based on the user input. /// </summary> /// <param name="FormData">The user input from the client.</param> /// <returns>A view model to use to display a list of crashes.</returns> /// <remarks>The filtering mechanism works by generating a fluent query to describe the set of crashes we wish to view. Basically, 'Select.Where().Where().Where().Where()' etc. /// The filtering is applied in the following order: /// 1. Get all crashes. /// 2. Filter between the start and end dates. /// 3. Apply the search query. /// 4. Filter by the branch. /// 5. Filter by the game name. /// 6. Filter by the type of reports to view. /// 7. Filter by the user group. /// 8. Sort the results by the sort term. /// 9. Take one page of results.</remarks> public CrashesViewModel GetResults(FormHelper FormData) { IQueryable <Crash> Results = null; int Skip = (FormData.Page - 1) * FormData.PageSize; int Take = FormData.PageSize; Results = ListAll(); Results = FilterByDate(Results, FormData.DateFrom, FormData.DateTo); // Grab Results if (!string.IsNullOrEmpty(FormData.SearchQuery)) { Results = Search(Results, FormData.SearchQuery); } // Start Filtering the results // Filter by BranchName if (!string.IsNullOrEmpty(FormData.BranchName)) { if (FormData.BranchName.StartsWith("-")) { Results = ( from CrashDetail in Results where !CrashDetail.Branch.Contains(FormData.BranchName.Substring(1)) select CrashDetail ); } else { Results = ( from CrashDetail in Results where CrashDetail.Branch.Contains(FormData.BranchName) select CrashDetail ); } } // Filter by GameName if (!string.IsNullOrEmpty(FormData.GameName)) { if (FormData.GameName.StartsWith("-")) { Results = ( from CrashDetail in Results where !CrashDetail.GameName.Contains(FormData.GameName.Substring(1)) select CrashDetail ); } else { Results = ( from CrashDetail in Results where CrashDetail.GameName.Contains(FormData.GameName) select CrashDetail ); } } // Filter by Crash Type if (FormData.CrashType != "All") { switch (FormData.CrashType) { case "Crashes": Results = Results.Where(CrashInstance => CrashInstance.CrashType == 1); break; case "Assert": Results = Results.Where(CrashInstance => CrashInstance.CrashType == 2); break; case "Ensure": Results = Results.Where(CrashInstance => CrashInstance.CrashType == 3); break; case "CrashesAsserts": Results = Results.Where(CrashInstance => CrashInstance.CrashType == 1 || CrashInstance.CrashType == 2); break; } } // Get UserGroup ResultCounts Dictionary <string, int> GroupCounts = GetCountsByGroup(Results); // Filter by user group if present int UserGroupId; if (!string.IsNullOrEmpty(FormData.UserGroup)) { UserGroupId = FindOrAddUserGroup(FormData.UserGroup); } else { UserGroupId = 1; } Results = ( from CrashDetail in Results from UserDetail in CrashRepositoryDataContext.Users where UserDetail.UserGroupId == UserGroupId && (CrashDetail.UserNameId == UserDetail.Id || CrashDetail.UserName == UserDetail.UserName) select CrashDetail ); // Pass in the results and return them sorted properly Results = GetSortedResults(Results, FormData.SortTerm, (FormData.SortOrder == "Descending")); // Get the Count for pagination int ResultCount = Results.Count(); // Grab just the results we want to display on this page Results = Results.Skip(Skip).Take(Take); // Process call stack for display foreach (Crash CrashInstance in Results) { // Put callstacks into an list so we can access them line by line in the view CrashInstance.CallStackContainer = GetCallStack(CrashInstance); } return(new CrashesViewModel { Results = Results, PagingInfo = new PagingInfo { CurrentPage = FormData.Page, PageSize = FormData.PageSize, TotalResults = ResultCount }, SortOrder = FormData.SortOrder, SortTerm = FormData.SortTerm, UserGroup = FormData.UserGroup, CrashType = FormData.CrashType, SearchQuery = FormData.SearchQuery, DateFrom = (long)(FormData.DateFrom - CrashesViewModel.Epoch).TotalMilliseconds, DateTo = (long)(FormData.DateTo - CrashesViewModel.Epoch).TotalMilliseconds, BranchName = FormData.BranchName, GameName = FormData.GameName, GroupCounts = GroupCounts, }); }