// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Web;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Tools.CrashReporter.CrashReportCommon;
using Tools.CrashReporter.CrashReportWebSite.Properties;
using Tools.CrashReporter.CrashReportWebSite.ViewModels;
namespace Tools.CrashReporter.CrashReportWebSite.DataModels
{
public partial class Bugg
{
public int CrashesInTimeFrameGroup { get; set; }
public int CrashesInTimeFrameAll { get; set; }
public int NumberOfUniqueMachines { get; set; }
///
public SortedSet AffectedPlatforms { get; set; }
///
public SortedSet AffectedVersions { get; set; }
/// 4.4, 4.5 and so.
public SortedSet AffectedMajorVersions { get; set; }
///
public SortedSet BranchesFoundIn { get; set; }
///
public string JiraSummary { get; set; }
/// The first line of the callstack
string ToJiraSummary = "";
///
public string JiraComponentsText { get; set; }
///
public string JiraResolution { get; set; }
///
public string JiraFixVersionsText { get; set; }
///
public string JiraFixCL { get; set; }
public List GenericFunctionCalls = null;
public List FunctionCalls = new List();
List ToJiraDescriptions = new List();
List ToJiraFunctionCalls = null;
///
public int LatestCLAffected { get; set; }
///
public string LatestOSAffected { get; set; }
///
public string LatestCrashSummary { get; set; }
/// Branches in JIRA
HashSet ToJiraBranches = null;
/// Versions in JIRA
List ToJiraVersions = null;
/// Platforms in JIRA
List ToJiraPlatforms = null;
/// Branches in jira summary fortnite
List ToBranchName = null;
/// The CL of the oldest build when this bugg is occurring
int ToJiraFirstCLAffected { get; set; }
public string JiraProject { get; set; }
///
public string GetCrashTypeAsString()
{
if (CrashType == 1)
{
return "Crash";
}
else if (CrashType == 2)
{
return "Assert";
}
else if (CrashType == 3)
{
return "Ensure";
}
return "Unknown";
}
///
/// Prepares Bugg for JIRA
///
///
public void PrepareBuggForJira(List CrashesForBugg)
{
var jiraConnection = JiraConnection.Get();
this.AffectedVersions = new SortedSet();
this.AffectedMajorVersions = new SortedSet(); // 4.4, 4.5 and so on
this.BranchesFoundIn = new SortedSet();
this.AffectedPlatforms = new SortedSet();
var hashSetDescriptions = new HashSet();
var machineIds = new HashSet();
var firstClAffected = int.MaxValue;
foreach (var crashDataModel in CrashesForBugg)
{
// Only add machine if the number has 32 characters
if (crashDataModel.ComputerName != null && crashDataModel.ComputerName.Length == 32)
{
machineIds.Add(crashDataModel.ComputerName);
if (crashDataModel.Description.Length > 4)
{
hashSetDescriptions.Add(crashDataModel.Description);
}
}
if (!string.IsNullOrEmpty(crashDataModel.BuildVersion))
{
this.AffectedVersions.Add(crashDataModel.BuildVersion);
}
// Depot || Stream
if (!string.IsNullOrEmpty(crashDataModel.Branch))
{
this.BranchesFoundIn.Add(crashDataModel.Branch);
}
var CrashBuiltFromCl = 0;
int.TryParse(crashDataModel.ChangelistVersion, out CrashBuiltFromCl);
if (CrashBuiltFromCl > 0)
{
firstClAffected = Math.Min(firstClAffected, CrashBuiltFromCl);
}
if (!string.IsNullOrEmpty(crashDataModel.PlatformName))
{
// Platform = "Platform [Desc]";
var platSubs = crashDataModel.PlatformName.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
if (platSubs.Length >= 1)
{
this.AffectedPlatforms.Add(platSubs[0]);
}
}
}
//ToJiraDescriptons
foreach (var line in hashSetDescriptions)
{
var listItem = "- " + HttpUtility.HtmlEncode(line);
ToJiraDescriptions.Add(listItem);
}
this.ToJiraFirstCLAffected = firstClAffected;
if (this.AffectedVersions.Count > 0)
{
this.BuildVersion = this.AffectedVersions.Last(); // Latest Version Affected
}
foreach (var affectedBuild in this.AffectedVersions)
{
var subs = affectedBuild.Split(".".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
if (subs.Length >= 2)
{
var majorVersion = subs[0] + "." + subs[1];
this.AffectedMajorVersions.Add(majorVersion);
}
}
var bv = this.BuildVersion;
var latestClAffected = CrashesForBugg. // CL of the latest build
Where(Crash => Crash.BuildVersion == bv).
Max(Crash => Crash.ChangelistVersion);
var ILatestCLAffected = -1;
int.TryParse(latestClAffected, out ILatestCLAffected);
this.LatestCLAffected = ILatestCLAffected; // Latest CL Affected
var latestOsAffected = CrashesForBugg.OrderByDescending(Crash => Crash.TimeOfCrash).First().PlatformName;
this.LatestOSAffected = latestOsAffected; // Latest Environment Affected
// ToJiraSummary
var functionCalls = new CallStackContainer(CrashesForBugg.First().CrashType, CrashesForBugg.First().RawCallStack, CrashesForBugg.First().PlatformName).GetFunctionCallsForJira();
if (functionCalls.Count > 0)
{
this.ToJiraSummary = functionCalls[0];
this.ToJiraFunctionCalls = functionCalls;
}
else
{
this.ToJiraSummary = "No valid callstack found";
this.ToJiraFunctionCalls = new List();
}
// ToJiraVersions
this.ToJiraVersions = new List();
foreach (var version in this.AffectedMajorVersions)
{
var bValid = jiraConnection.GetNameToVersions().ContainsKey(version);
if (bValid)
{
this.ToJiraVersions.Add(jiraConnection.GetNameToVersions()[version]);
}
}
// ToJiraBranches
this.ToJiraBranches = new HashSet();
foreach (var branchName in this.BranchesFoundIn)
{
if (!string.IsNullOrEmpty(branchName))
{
// Stream
if (branchName.StartsWith("//"))
{
this.ToJiraBranches.Add(branchName);
}
// Depot
else
{
this.ToJiraBranches.Add(CrashReporterConstants.P4_DEPOT_PREFIX + branchName);
}
}
}
// ToJiraPlatforms
this.ToJiraPlatforms = new List();
foreach (var platform in this.AffectedPlatforms)
{
bool bValid = jiraConnection.GetNameToPlatform().ContainsKey(platform);
if (bValid)
{
this.ToJiraPlatforms.Add(jiraConnection.GetNameToPlatform()[platform]);
}
}
// ToJiraPlatforms
this.ToBranchName = new List();
foreach (var BranchName in this.BranchesFoundIn)
{
bool bValid = jiraConnection.GetNameToBranch().ContainsKey(BranchName);
if (bValid)
{
this.ToBranchName.Add(jiraConnection.GetNameToBranch()[BranchName]);
}
}
}
///
/// Create a new Jira for this bug
///
public void CopyToJira()
{
var jc = JiraConnection.Get();
Dictionary issueFields;
switch (this.JiraProject)
{
case "UE":
issueFields = CreateGeneralIssue(jc);
break;
case "FORT":
issueFields = CreateFortniteIssue(jc);
break;
case "ORION":
issueFields = CreateOrionIssue(jc);
break;
default:
issueFields = CreateProjectIssue(jc, this.JiraProject);
break;
}
if (jc.CanBeUsed() && string.IsNullOrEmpty(this.TTPID))
{
// Additional Info URL / Link to Crash/Bugg
var key = jc.AddJiraTicket(issueFields);
if (!string.IsNullOrEmpty(key))
{
TTPID = key;
}
}
}
///
///
///
///
public Dictionary CreateGeneralIssue(JiraConnection jc)
{
var fields = new Dictionary();
fields.Add("project", new Dictionary { { "id", 11205 } }); // UE
fields.Add("summary", "[CrashReport] " + ToJiraSummary); // Call Stack, Line 1
fields.Add("description", string.Join("\r\n", ToJiraDescriptions)); // Description
fields.Add("issuetype", new Dictionary { { "id", "1" } }); // Bug
fields.Add("labels", new string[] { "Crash", "liveissue" }); // Crash, live issue
fields.Add("customfield_11500", ToJiraFirstCLAffected); // Changelist # / Found Changelist
fields.Add("environment", LatestOSAffected); // Platform
// Components
var SupportComponent = jc.GetNameToComponents()["Support"];
fields.Add("components", new object[] { SupportComponent });
// ToJiraVersions
fields.Add("versions", ToJiraVersions);
// ToJiraBranches
fields.Add("customfield_12402", ToJiraBranches.ToList()); // NewBranchFoundIn
// ToJiraPlatforms
fields.Add("customfield_11203", ToJiraPlatforms);
// Callstack customfield_11807
string JiraCallstack = "{noformat}" + string.Join("\r\n", ToJiraFunctionCalls) + "{noformat}";
fields.Add("customfield_11807", JiraCallstack); // Callstack
string BuggLink = "http://Crashreporter/Buggs/Show/" + Id;
fields.Add("customfield_11205", BuggLink);
return fields;
}
///
///
///
///
///
public Dictionary CreateFortniteIssue(JiraConnection jc)
{
var fields = new Dictionary();
fields.Add("summary", "[CrashReport] " + ToJiraSummary); // Call Stack, Line 1
fields.Add("description", string.Join("\r\n", ToJiraDescriptions)); // Description
fields.Add("project", new Dictionary { { "id", 10600 } });
fields.Add("issuetype", new Dictionary { { "id", "1" } }); // Bug
//branch found in - required = false
fields.Add("customfield_11201", ToBranchName.ToList());
//Platforms - required = false
fields.Add("customfield_11203", new List() { new Dictionary() { { "self", Settings.Default.JiraDeploymentAddress + "/customFieldOption/11425" }, { "value", "PC" } } });
// Callstack customfield_11807
string JiraCallstack = "{noformat}" + string.Join("\r\n", ToJiraFunctionCalls) + "{noformat}";
fields.Add("customfield_11807", JiraCallstack); // Callstack
return fields;
}
///
///
///
///
///
public Dictionary CreateOrionIssue(JiraConnection jc)
{
var fields = new Dictionary();
fields.Add("summary", "[CrashReport] " + ToJiraSummary); // Call Stack, Line 1
fields.Add("description", string.Join("\r\n", ToJiraDescriptions)); // Description
fields.Add("project", new Dictionary { { "id", 10700 } });
fields.Add("issuetype", new Dictionary { { "id", "1" } }); // Bug
fields.Add("components", new object[]{new Dictionary{{"id", "14604"}}});
return fields;
}
///
/// Create an issue description from a project ID
///
///
///
///
public Dictionary CreateProjectIssue(JiraConnection jc, string projectId)
{
var response = jc.JiraRequest("/issue/createmeta?projectKeys="+ projectId +"&expand=projects.issuetypes.fields",
JiraConnection.JiraMethod.GET, null, HttpStatusCode.OK);
var issueFields = new Dictionary();
issueFields.Add("summary", "[CrashReport] " + ToJiraSummary); // Call Stack, Line 1
issueFields.Add("description", string.Join("\r\n", ToJiraDescriptions)); // Description
using (var responseReader = new StreamReader(response.GetResponseStream()))
{
var responseText = responseReader.ReadToEnd();
JObject jsonObject = JObject.Parse(responseText);
bool fields = jsonObject["projects"][0]["issuetypes"].Any();
issueFields.Add("project", new Dictionary { { "id", jsonObject["projects"][0]["id"].ToObject() } });
if (!fields)
return issueFields;
foreach (
JToken issuetype in
jsonObject["projects"][0]["issuetypes"])//.[0]["fields"])
{
//Only fill required fields
if (issuetype["subtask"].ToObject() == true)
continue;
JToken field = issuetype["fields"];
if (field["required"].ToObject() == true)
{
//don't fill fields with a default value
if (field["hasDefaultValue"].ToObject() == true)
continue;
JToken outValue;
//if (field.Value["schema"]["type"].ToObject() == "array")
//{
// if (field.Value["allowedValues"] != null)
// {
// //don't add the same key twice
// if (issueFields.ContainsKey(field.Key))
// continue;
// issueFields.Add(field.Key, new object[]
// {
// new Dictionary
// {
// {"id", field.Value["allowedValues"][0]["id"].ToObject()}
// }
// });
// }
//}
//else
//{
// if (obj.TryGetValue("allowedValues", out outValue))
// {
// //don't add the same key twice
// if (issueFields.ContainsKey(field.Key))
// continue;
// issueFields.Add(field.Key,
// new Dictionary
// {
// {"id", field.Value["allowedValues"][0]["id"].ToObject()}
// });
// }
//}
}
}
}
return issueFields;
}
/// Returns concatenated string of fields from the specified JIRA list.
public string GetFieldsFrom(List jiraList, string fieldName)
{
var results = "";
foreach (var obj in jiraList)
{
var dict = (Dictionary)obj;
try
{
results += (string)dict[fieldName];
results += " ";
}
catch (System.Exception /*E*/ )
{
}
}
return results;
}
/// Creates a previews of the bugg, for verifying JIRA's fields.
public string ToTooltip()
{
string NL = "
";
string Tooltip = NL;
Tooltip += "Project: UE" + NL;
Tooltip += "Summary: " + ToJiraSummary + NL;
Tooltip += "Description: " + NL + string.Join(NL, ToJiraDescriptions) + NL;
Tooltip += "Issuetype: " + "1 (bug)" + NL;
Tooltip += "Labels: " + "Crash" + NL;
Tooltip += "Changelist # / Found Changelist: " + ToJiraFirstCLAffected + NL;
Tooltip += "LatestOSAffected: " + LatestOSAffected + NL;
// "name"
var JiraVersions = GetFieldsFrom(ToJiraVersions, "name");
Tooltip += "JiraVersions: " + JiraVersions + NL;
// "value"
var jiraBranches = "";
foreach (var Branch in ToJiraBranches)
{
jiraBranches += Branch + ", ";
}
Tooltip += "JiraBranches: " + jiraBranches + NL;
// "value"
var JiraPlatforms = GetFieldsFrom(ToJiraPlatforms, "value");
Tooltip += "JiraPlatforms: " + JiraPlatforms + NL;
var JiraCallstack = "Callstack:" + NL + string.Join(NL, ToJiraFunctionCalls) + NL;
Tooltip += JiraCallstack;
return Tooltip;
}
///
///
///
public string GetAffectedVersions()
{
if (AffectedVersions.Count == 1)
{
return AffectedVersions.Max;
}
else
{
return AffectedVersions.Min + " - " + AffectedVersions.Max;
}
}
///
///
///
///
public string GetLifeSpan()
{
TimeSpan LifeSpan = TimeOfLastCrash.Value - TimeOfFirstCrash.Value;
// Only to visualize the life span, accuracy not so important here.
int NumMonths = LifeSpan.Days / 30;
int NumDays = LifeSpan.Days % 30;
if (NumMonths > 0)
{
return string.Format("{0} month(s) {1} day(s)", NumMonths, NumDays);
}
else if (NumDays > 0)
{
return string.Format("{0} day(s)", NumDays);
}
else
{
return "Less than one day";
}
}
}
}