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 priorities; SortedList components; SortedList milestones; SortedList types; string fixCL; public JiraImplementation() { jiraProxy = new JiraSoapServiceService(); jiraToken = jiraProxy.login(reporterUsername, reporterPassword); jiraProject = Properties.Settings.Default.JiraProject; priorities = new SortedList(); components = new SortedList(); milestones = new SortedList(); types = new SortedList(); fixCL = null; } private void Relogin() { jiraToken = jiraProxy.login(reporterUsername, reporterPassword); } /// /// Look for CustomField "nb of occurences - editor" in jira /// /// Custom field id 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; } /// /// Gets id of "urgent" priority in Jira /// /// priority id 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; } /// /// Look for "Crash" issue type in jira /// /// Issue type id 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 ""; } /// /// Adds a crash on Jira /// /// Bug inforamtions passed by the user /// id of the issue on Jira 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 crashCustomFields = new List(); 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 GetAllMilestones() { List miles = new List(); //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 getAllTypes() { RemoteIssueType[] RemoteObject = jiraProxy.getIssueTypes(jiraToken); List Names = new List(); 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 getAllUsers() { StreamReader SR; string S; List users = new List(); 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 GetAllComponents() { RemoteComponent[] RemoteObject = jiraProxy.getComponents(jiraToken, jiraProject); List Names = new List(); string strTemp; foreach (RemoteComponent value in RemoteObject) { strTemp = value.name; Names.Add(strTemp); components.Add(strTemp, value); } return Names; } public List GetAllPriorities() { RemotePriority[] rp = jiraProxy.getPriorities(jiraToken); List Names = new List(); 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 } }