493 lines
20 KiB
C#
493 lines
20 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using CrashDumpSender.JiraSoap;
|
|
using System.Collections;
|
|
using System.IO;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace CrashDumpSender.BugTrackSystem.Implementations
|
|
{
|
|
class JiraImplementation : IBugTracksystem
|
|
{
|
|
|
|
JiraSoapServiceService jiraProxy = null;
|
|
string reporterUsername = "crashdumpsender";
|
|
string reporterPassword = "justdance5";
|
|
string jiraToken;
|
|
string jiraProject;
|
|
SortedList<string, RemotePriority> priorities;
|
|
SortedList<string, RemoteComponent> components;
|
|
SortedList<string, RemoteCustomFieldValue> milestones;
|
|
SortedList<string, RemoteIssueType> types;
|
|
string fixCL;
|
|
|
|
public JiraImplementation()
|
|
{
|
|
jiraProxy = new JiraSoapServiceService();
|
|
jiraToken = jiraProxy.login(reporterUsername, reporterPassword);
|
|
|
|
jiraProject = Properties.Settings.Default.JiraProject;
|
|
priorities = new SortedList<string, RemotePriority>();
|
|
components = new SortedList<string, RemoteComponent>();
|
|
milestones = new SortedList<string, RemoteCustomFieldValue>();
|
|
types = new SortedList<string, RemoteIssueType>();
|
|
fixCL = null;
|
|
}
|
|
|
|
private void Relogin()
|
|
{
|
|
jiraToken = jiraProxy.login(reporterUsername, reporterPassword);
|
|
}
|
|
/// <summary>
|
|
/// Look for CustomField "nb of occurences - editor" in jira
|
|
/// </summary>
|
|
/// <returns>Custom field id</returns>
|
|
private string getNbOccurencesEditor()
|
|
{
|
|
RemoteField[] RemoteObject = jiraProxy.getCustomFields(jiraToken);
|
|
foreach (RemoteField value in RemoteObject)
|
|
{
|
|
if (value.name.Equals("nb of occurences - tools", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return value.id;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets id of "urgent" priority in Jira
|
|
/// </summary>
|
|
/// <returns>priority id</returns>
|
|
private string getCrashPriority()
|
|
{
|
|
RemotePriority[] RemoteObject = jiraProxy.getPriorities(jiraToken);
|
|
foreach (RemotePriority value in RemoteObject)
|
|
{
|
|
if (value.name.ToLower().Equals(CrashDumpSender.Properties.Settings.Default.Severity))
|
|
{
|
|
return value.id;
|
|
}
|
|
}
|
|
return null;
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Look for "Crash" issue type in jira
|
|
/// </summary>
|
|
/// <returns>Issue type id</returns>
|
|
private string getCrashType()
|
|
{
|
|
RemoteIssueType[] RemoteObject = jiraProxy.getIssueTypes(jiraToken);
|
|
foreach (RemoteIssueType value in RemoteObject)
|
|
{
|
|
if (value.name.ToLower().Trim().Equals(("crash")))
|
|
{
|
|
return value.id;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private string getCustomFieldId(string fieldName)
|
|
{
|
|
RemoteField[] RemoteObject = jiraProxy.getCustomFields(jiraToken);
|
|
foreach (RemoteField value in RemoteObject)
|
|
{
|
|
if (value.name.Trim().Equals(fieldName, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return value.id;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private RemoteCustomFieldValue CreateCustomFieldValue(string name, string value)
|
|
{
|
|
string fieldId = getCustomFieldId(name);
|
|
if (string.IsNullOrEmpty(fieldId)) throw new System.Exception("Unknown JIRA custom field " + name);
|
|
|
|
RemoteCustomFieldValue customFieldValue = new RemoteCustomFieldValue();
|
|
customFieldValue.customfieldId = fieldId;
|
|
customFieldValue.values = new string[] { value };
|
|
|
|
return customFieldValue;
|
|
}
|
|
|
|
#region IBugTracksystem Members
|
|
|
|
// Unused
|
|
public string AddIssue(BugInfo bug)
|
|
{
|
|
return "";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a crash on Jira
|
|
/// </summary>
|
|
/// <param name="bug">Bug inforamtions passed by the user</param>
|
|
/// <returns>id of the issue on Jira</returns>
|
|
public string AddCrash(BugInfo bug)
|
|
{
|
|
Relogin();
|
|
RemoteIssue ri; // Remote Issue to update or Create
|
|
|
|
// First we build a JQL search string to find duplicates
|
|
// summary of crashes are a CRC of the callstack that's why we look for an issue
|
|
// with the same summary
|
|
string ExistingIssue = string.Format("project = {0} AND MD5 ~ {1}",
|
|
jiraProject,
|
|
bug.MD5);
|
|
|
|
// Do the search on Jira
|
|
RemoteIssue[] ExistingIssues = jiraProxy.getIssuesFromJqlSearch(jiraToken, ExistingIssue, 10);
|
|
|
|
// look for the CustomField ID of "Nb of Occurences - PC"
|
|
string NbOccEditorId = getNbOccurencesEditor();
|
|
|
|
if (ExistingIssues.Length > 0)
|
|
{
|
|
// Crash with same Stack exists
|
|
// => Update existing issue
|
|
ri = ExistingIssues[0];
|
|
|
|
// Create a field of ID "Nb of Occurences - PC" to update or create
|
|
RemoteFieldValue nbOccPc = new RemoteFieldValue();
|
|
nbOccPc.id = NbOccEditorId;
|
|
|
|
// Use a linq search to find if the issue already has a "Nb of Occurences - PC" Custom field
|
|
var NbOccPCField = from x
|
|
in ri.customFieldValues
|
|
where x.customfieldId.Equals(NbOccEditorId)
|
|
select x;
|
|
|
|
// Check if we can update issue with new value
|
|
bool updateIssue = false;
|
|
if (NbOccPCField.Count() == 0)
|
|
{
|
|
// field wasn't present before, now we now that the crash occured at least 2 times
|
|
nbOccPc.values = new string[] { "2" };
|
|
updateIssue = true;
|
|
|
|
}
|
|
else
|
|
{
|
|
int nbOccurences;
|
|
if (int.TryParse(NbOccPCField.Single().values[0], out nbOccurences))
|
|
{
|
|
nbOccurences++;
|
|
nbOccPc.values = new string[] { nbOccurences.ToString() };
|
|
updateIssue = true;
|
|
}
|
|
|
|
}
|
|
RemoteStatus[] statuses = jiraProxy.getStatuses(jiraToken);
|
|
var ClosedStatus = from x in statuses where x.name.ToLower().Trim().Equals("closed") select x;
|
|
var ResolvedStatus = from x in statuses where x.name.ToLower().Trim().Equals("resolved") select x;
|
|
if (ri.status.Equals(ClosedStatus.Single().id) || ri.status.Equals(ResolvedStatus.Single().id))
|
|
{
|
|
if (!string.IsNullOrEmpty(bug.changelist))
|
|
{
|
|
int CurCL = Convert.ToInt32(bug.changelist);
|
|
string clId = getCustomFieldId("changelist");
|
|
string FixedByCodeVersion = getFixedByCodeCustomFieldId();
|
|
bool bMustReopen = false;
|
|
if (!string.IsNullOrEmpty(clId))
|
|
{
|
|
foreach (RemoteCustomFieldValue rcfv in ri.customFieldValues)
|
|
{
|
|
if (rcfv.customfieldId.Equals(clId))
|
|
{
|
|
int bugCL = Convert.ToInt32(rcfv.values[0]);
|
|
if (CurCL > bugCL)
|
|
{
|
|
// We found a crash occuring on a new changelist than the original crash.
|
|
// So flag the issue as *potentially* must reopen, but continue cycling through custom fields
|
|
// as we may not know yet the changelist it was fixed on ("Fixed by code" customfield)
|
|
bMustReopen = true;
|
|
}
|
|
}
|
|
if (rcfv.customfieldId.Equals(FixedByCodeVersion))
|
|
{
|
|
// We found a "Fixed by code custom field
|
|
if (string.IsNullOrEmpty(rcfv.values[0]))
|
|
{
|
|
// if we found an empty value, then continue
|
|
// The decision of reopening the bug is now the based on the "changelist" custom field
|
|
continue;
|
|
}
|
|
// Check the changelist the crash is supposed to be fixed
|
|
int fixedByCode = Convert.ToInt32(rcfv.values[0]);
|
|
if (CurCL > fixedByCode)
|
|
{
|
|
// if the engine cl detected is greater than the fixed by code value then reopen the issue
|
|
bMustReopen = true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// if the engine cl detected is lower than the fixed by code value then DON'T reopen the issue !!
|
|
bMustReopen = false;
|
|
fixCL = fixedByCode.ToString();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (bMustReopen)
|
|
{
|
|
RemoteNamedObject[] ro = jiraProxy.getAvailableActions(jiraToken, ri.key);
|
|
var ApproveBug = from s in ro
|
|
where s.name.ToLower().Trim().Equals("reopen")
|
|
select s;
|
|
ri = jiraProxy.progressWorkflowAction(jiraToken, ri.key, ApproveBug.Single().id, null);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (updateIssue)
|
|
{
|
|
RemoteFieldValue[] newValues = new RemoteFieldValue[] { nbOccPc };
|
|
ri = jiraProxy.updateIssue(jiraToken, ri.key, newValues);
|
|
|
|
// add comments
|
|
RemoteComment AdditionalComment = new RemoteComment();
|
|
|
|
int indexCamInfo = bug.comments.IndexOf("Camera Info");
|
|
string Comments;
|
|
if (indexCamInfo != -1)
|
|
Comments = bug.comments.Substring(indexCamInfo);
|
|
else
|
|
Comments = bug.comments;
|
|
Comments = Comments.Replace("-----------------------------\r\n", "");
|
|
AdditionalComment.body = string.Concat("*Reporter:* ", bug.reporter, "\n\n",
|
|
"*Engine:* ", bug.changelist, "\n\n",
|
|
"*Call stack & Warnings:* ", "\n",
|
|
Comments , "\n\n",
|
|
"*Steps to reproduce:* ", "\n",
|
|
bug.stepsToReproduce);
|
|
|
|
jiraProxy.addComment(jiraToken, ri.key, AdditionalComment);
|
|
if (bug.attachements != null && bug.attachements.Length > 0)
|
|
{
|
|
foreach (string str in bug.attachements)
|
|
{
|
|
string[] fileNames = new string[] { Path.GetFileName(str) };
|
|
string[] picData = new string[] { Convert.ToBase64String(File.ReadAllBytes(str)) };
|
|
jiraProxy.addBase64EncodedAttachmentsToIssue(jiraToken, ri.key, fileNames, picData);
|
|
}
|
|
}
|
|
return ri.key;
|
|
|
|
|
|
}
|
|
// ELSE we're not able to update an existing issue with same stack
|
|
// in order not to lose the crash info, we allow the creation of a new crash in Jira
|
|
|
|
}
|
|
|
|
// Create new Issue
|
|
ri = new RemoteIssue();
|
|
ri.summary = bug.summary;
|
|
|
|
// Recover crash type from jira
|
|
ri.type = getCrashType();
|
|
|
|
// Assign issue to our project
|
|
ri.project = jiraProject;
|
|
|
|
int iSeparateBar = bug.callstack.IndexOf("-----------------------------");
|
|
if (iSeparateBar != -1)
|
|
{
|
|
bug.description += "\n" + bug.callstack.Substring(iSeparateBar);
|
|
bug.callstack = bug.callstack.Substring(0, iSeparateBar);
|
|
}
|
|
// Adds description
|
|
ri.description = string.Format("*Reporter:* {0}\n\n{1}",
|
|
Environment.UserName,
|
|
bug.description);
|
|
|
|
// Check if an assignee was given
|
|
if (!string.IsNullOrEmpty(bug.assignee))
|
|
ri.assignee = bug.assignee;
|
|
|
|
// try to add the "Engine" component to the crash
|
|
RemoteComponent rcmpnt = GetComponent(bug.component);
|
|
if (rcmpnt != null)
|
|
{
|
|
ri.components = new RemoteComponent[] { rcmpnt };
|
|
}
|
|
|
|
// It doesn't exist (hidden?) in Ubisoft JIRA databases
|
|
// ri.priority = getCrashPriority();
|
|
|
|
// Prepare a list of custom fields to add to the issue
|
|
List<RemoteCustomFieldValue> crashCustomFields = new List<RemoteCustomFieldValue>();
|
|
crashCustomFields.Add(CreateCustomFieldValue("callstack", bug.callstack));
|
|
crashCustomFields.Add(CreateCustomFieldValue("MD5", bug.MD5));
|
|
crashCustomFields.Add(CreateCustomFieldValue("severity", bug.severity));
|
|
crashCustomFields.Add(CreateCustomFieldValue("probability", bug.probability));
|
|
crashCustomFields.Add(CreateCustomFieldValue("technical reproducibility", bug.technicalReproducibility));
|
|
crashCustomFields.Add(CreateCustomFieldValue("milestone", bug.milestone));
|
|
crashCustomFields.Add(CreateCustomFieldValue("platform/module", bug.platformModule));
|
|
crashCustomFields.Add(CreateCustomFieldValue("steps to reproduce", bug.stepsToReproduce));
|
|
crashCustomFields.Add(CreateCustomFieldValue("nb of occurences - tools", "1"));
|
|
crashCustomFields.Add(CreateCustomFieldValue("map", "None"));
|
|
crashCustomFields.Add(CreateCustomFieldValue("teams", "None"));
|
|
crashCustomFields.Add(CreateCustomFieldValue("Mode Specific", "NONE"));
|
|
|
|
// if we managed to custom fields, assign them to the issue
|
|
if (crashCustomFields.Count > 0)
|
|
{
|
|
ri.customFieldValues = crashCustomFields.ToArray();
|
|
}
|
|
|
|
// Create the new issue in Jira !
|
|
RemoteIssue NewlyCreatedIssue = jiraProxy.createIssue(jiraToken, ri);
|
|
|
|
if (bug.attachements != null && bug.attachements.Length > 0)
|
|
{
|
|
foreach (string str in bug.attachements)
|
|
{
|
|
string[] fileNames = new string[] { Path.GetFileName(str) };
|
|
string[] picData = new string[] { Convert.ToBase64String(File.ReadAllBytes(str)) };
|
|
jiraProxy.addBase64EncodedAttachmentsToIssue(jiraToken, NewlyCreatedIssue.key, fileNames, picData);
|
|
}
|
|
}
|
|
|
|
return NewlyCreatedIssue.key;
|
|
}
|
|
|
|
private string getFixedByCodeCustomFieldId()
|
|
{
|
|
RemoteField[] RemoteObject = jiraProxy.getCustomFields(jiraToken);
|
|
foreach (RemoteField value in RemoteObject)
|
|
{
|
|
if (value.name.ToLower().Trim().Equals(("fixed by code version")))
|
|
{
|
|
return value.id;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public List<string> GetAllMilestones()
|
|
{
|
|
List<string> miles = new List<string>();
|
|
//miles.Add("Go to prod");
|
|
RemoteVersion[] versions = jiraProxy.getVersions(jiraToken, jiraProject);
|
|
var VerNames = from x in versions orderby x.releaseDate descending select x;
|
|
|
|
foreach (RemoteVersion rv in VerNames.ToList())
|
|
{
|
|
miles.Add(rv.name);
|
|
}
|
|
return miles;
|
|
|
|
}
|
|
|
|
public List<string> getAllTypes()
|
|
{
|
|
RemoteIssueType[] RemoteObject = jiraProxy.getIssueTypes(jiraToken);
|
|
List<string> Names = new List<string>();
|
|
string strTemp;
|
|
foreach (RemoteIssueType value in RemoteObject)
|
|
{
|
|
strTemp = value.name;
|
|
Names.Add(strTemp);
|
|
types.Add(strTemp, value);
|
|
}
|
|
return Names;
|
|
}
|
|
|
|
private RemoteVersion getVersion(string versionlabel)
|
|
{
|
|
RemoteVersion[] versions = jiraProxy.getVersions(jiraToken, jiraProject);
|
|
var FoundVersion = from x in versions where x.name.Equals(versionlabel) select x;
|
|
if (FoundVersion.Count() == 1)
|
|
return FoundVersion.Single();
|
|
return null;
|
|
}
|
|
|
|
public List<string> getAllUsers()
|
|
{
|
|
StreamReader SR;
|
|
string S;
|
|
List<string> users = new List<string>();
|
|
if (File.Exists("jiraUsers.txt"))
|
|
{
|
|
SR = File.OpenText("jiraUsers.txt");
|
|
S = SR.ReadToEnd();
|
|
|
|
string[] lines = Regex.Split(S, "\r\n");
|
|
|
|
foreach (string user in lines)
|
|
users.Add(user);
|
|
|
|
SR.Close();
|
|
}
|
|
return users;
|
|
}
|
|
|
|
public RemoteComponent GetComponent(string ComponentName)
|
|
{
|
|
RemoteComponent[] RemoteObject = jiraProxy.getComponents(jiraToken, jiraProject);
|
|
string temp = ComponentName.ToLower().Trim();
|
|
foreach (RemoteComponent value in RemoteObject)
|
|
{
|
|
if (temp.Equals(value.name.ToLower()))
|
|
{
|
|
return value;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public List<string> GetAllComponents()
|
|
{
|
|
RemoteComponent[] RemoteObject = jiraProxy.getComponents(jiraToken, jiraProject);
|
|
List<string> Names = new List<string>();
|
|
string strTemp;
|
|
foreach (RemoteComponent value in RemoteObject)
|
|
{
|
|
strTemp = value.name;
|
|
Names.Add(strTemp);
|
|
components.Add(strTemp, value);
|
|
}
|
|
return Names;
|
|
}
|
|
|
|
public List<string> GetAllPriorities()
|
|
{
|
|
RemotePriority[] rp = jiraProxy.getPriorities(jiraToken);
|
|
List<string> Names = new List<string>();
|
|
string strTemp;
|
|
foreach (RemotePriority p in rp)
|
|
{
|
|
strTemp = p.name;
|
|
Names.Add(strTemp);
|
|
priorities.Add(strTemp, p);
|
|
}
|
|
return Names;
|
|
}
|
|
|
|
public string GetName()
|
|
{
|
|
return "Jira";
|
|
}
|
|
|
|
public string GetLinkToIssue(string IssueNumber)
|
|
{
|
|
return jiraProxy.Url.Replace("/rpc/soap/jirasoapservice-v2", "/browse/") + IssueNumber;
|
|
}
|
|
|
|
public string IssueIsFixed()
|
|
{
|
|
return fixCL;
|
|
}
|
|
#endregion
|
|
}
|
|
}
|