/// <summary> /// Returns a single JIRA issue by its KEY. Includes all fields and child collections (e.g. comments) /// </summary> /// <param name="issueKey">The JIRA issue key</param> /// <param name="jiraCreateMetaData">The creation meta-data</param> /// <returns>The JIRA issue object</returns> public JiraIssue GetIssueByKey(string issueKey, JiraCreateMetaData jiraCreateMetaData) { string result = RunQuery(resource: JiraResource.issue, argument: issueKey, method: "GET"); LogTraceEvent(this.eventLog, result, EventLogEntryType.SuccessAudit); //First deserialize into a generic JObject; JObject jIssue = JObject.Parse(result); //For the standard fields we use the standard deserializer JiraIssue issue = jIssue.ToObject <JiraIssue>(); //We need to get a handle on the fields meta-data for use later JObject jIssueTypeFields = null; if (jiraCreateMetaData != null) { JiraProject project = jiraCreateMetaData.Projects.FirstOrDefault(p => p.Id == issue.Fields.Project.Id); if (project != null) { IssueType issueType = project.IssueTypes.FirstOrDefault(t => t.Id == issue.Fields.IssueType.Id); if (issueType != null) { //See if we are missing any required properties jIssueTypeFields = issueType.Fields; } } } //Now we need to get the custom fields from the JObject directly JObject jFields = (JObject)jIssue["fields"]; if (jFields != null) { foreach (JProperty jProperty in jFields.Properties()) { //Make sure it's a custom field if (jProperty.Name.StartsWith(JiraCustomFieldValue.CUSTOM_FIELD_PREFIX)) { JiraCustomFieldValue customFieldValue = new JiraCustomFieldValue(jProperty.Name); CustomPropertyValue cpv = new CustomPropertyValue(); //We need to try and match the type of value if (jProperty.Value != null && jProperty.Value.Type != JTokenType.None && jProperty.Value.Type != JTokenType.Null) { LogTraceEvent(eventLog, String.Format("Found custom field '{0}' of JProperty type " + jProperty.Value.Type, jProperty.Name), EventLogEntryType.Information); if (jProperty.Value.Type == JTokenType.Array && jProperty.Value is JArray) { //Iterate through the list of values List <string> listOptionValueNames = new List <string>(); JArray jOptions = (JArray)jProperty.Value; foreach (JToken jToken in jOptions) { if (jToken is JObject) { JObject jOption = (JObject)jToken; //If we have an object that has an ID property then we have a multi-list if (jOption["id"] != null && jOption["id"].Type == JTokenType.String) { LogTraceEvent(eventLog, String.Format("Found custom field '{0}' that is an array of objects with ID field ({1})", jProperty.Name, jOption["id"].Type), EventLogEntryType.Information); string id = (string)jOption["id"]; if (!String.IsNullOrEmpty(id)) { //Need to convert into an integer and set the list value int idAsInt; if (Int32.TryParse(id, out idAsInt)) { LogTraceEvent(eventLog, String.Format("Looking for custom field value name that matches custom field {0} option value id {1}", jProperty.Name, idAsInt), EventLogEntryType.Information); string optionName = LookupCustomFieldOptionName(idAsInt, jIssueTypeFields, customFieldValue.CustomFieldName); listOptionValueNames.Add(optionName); LogTraceEvent(eventLog, String.Format("Found JIRA custom field {2} value name that matches custom field {0} option value id {1}", jProperty.Name, idAsInt, optionName), EventLogEntryType.Information); } } } } } if (listOptionValueNames.Count > 0) { cpv.CustomPropertyType = CustomPropertyValue.CustomPropertyTypeEnum.MultiList; cpv.MultiListValue = listOptionValueNames; } } else if (jProperty.Value.Type == JTokenType.Object) { //If we have an object that has an ID property then we have a single-list //If we have a 'name' property then we have a user field if (jProperty.Value is JObject) { JObject jOption = (JObject)jProperty.Value; if (jOption["id"] != null && jOption["id"].Type == JTokenType.String) { LogTraceEvent(eventLog, String.Format("Found custom field '{0}' that is an object with ID field ({1})", jProperty.Name, jOption["id"].Type), EventLogEntryType.Information); string id = (string)jOption["id"]; if (!String.IsNullOrEmpty(id)) { //Need to convert into an integer and get the name from the meta-data int idAsInt; if (Int32.TryParse(id, out idAsInt)) { LogTraceEvent(eventLog, String.Format("Looking for custom field value name that matches custom field {0} option value id {1}", jProperty.Name, idAsInt), EventLogEntryType.Information); string optionName = LookupCustomFieldOptionName(idAsInt, jIssueTypeFields, customFieldValue.CustomFieldName); cpv.CustomPropertyType = CustomPropertyValue.CustomPropertyTypeEnum.List; cpv.StringValue = optionName; LogTraceEvent(eventLog, String.Format("Found JIRA custom field {2} value name that matches custom field {0} option value id {1}", jProperty.Name, idAsInt, optionName), EventLogEntryType.Information); } } } else if (jOption["name"] != null && jOption["name"].Type == JTokenType.String) { LogTraceEvent(eventLog, String.Format("Found custom field '{0}' that is an object with NAME field ({1})", jProperty.Name, jOption["name"].Type), EventLogEntryType.Information); string username = (string)jOption["name"]; if (!String.IsNullOrEmpty(username)) { cpv.StringValue = username; cpv.CustomPropertyType = CustomPropertyValue.CustomPropertyTypeEnum.User; } } } } else if (jProperty.Value.Type == JTokenType.Boolean) { //Simple integer property cpv.CustomPropertyType = CustomPropertyValue.CustomPropertyTypeEnum.Boolean; cpv.BooleanValue = (bool?)jProperty.Value; } else if (jProperty.Value.Type == JTokenType.Integer) { //Simple integer property cpv.CustomPropertyType = CustomPropertyValue.CustomPropertyTypeEnum.Integer; cpv.IntegerValue = (int?)jProperty.Value; } else if (jProperty.Value.Type == JTokenType.Float) { //Simple float property cpv.CustomPropertyType = CustomPropertyValue.CustomPropertyTypeEnum.Decimal; cpv.DecimalValue = (decimal?)((float?)jProperty.Value); } else if (jProperty.Value.Type == JTokenType.Date) { //Simple date/time property cpv.CustomPropertyType = CustomPropertyValue.CustomPropertyTypeEnum.Date; cpv.DateTimeValue = (DateTime?)jProperty.Value; } else if (jProperty.Value.Type == JTokenType.String) { //Simple string property cpv.CustomPropertyType = CustomPropertyValue.CustomPropertyTypeEnum.Text; cpv.StringValue = (string)jProperty.Value; } } customFieldValue.Value = cpv; issue.CustomFieldValues.Add(customFieldValue); } } } return(issue); }
/// <summary> /// Adds a JIRA Issue into the system /// </summary> /// <param name="jiraIssue">The issue object</param> /// <param name="jiraCreateMetaData">The creation meta-data</param> /// public JiraIssueLink CreateIssue(JiraIssue jiraIssue, JiraCreateMetaData jiraCreateMetaData) { //First convert from the JiraIssue to a generic json object JObject jsonIssue = JObject.FromObject(jiraIssue); JObject jsonIssueFields = (JObject)jsonIssue["fields"]; JObject jIssueTypeFields = null; //Next validate the fields List <string> messages = new List <string>(); //Find the current project and issue type in the meta-data IssueType issueType = null; if (jiraCreateMetaData != null && jsonIssueFields != null) { JiraProject project = jiraCreateMetaData.Projects.FirstOrDefault(p => p.Id == jiraIssue.Fields.Project.Id); if (project != null) { issueType = project.IssueTypes.FirstOrDefault(t => t.Id == jiraIssue.Fields.IssueType.Id); if (issueType != null) { //See if we are missing any required properties (not custom fields at this point) jIssueTypeFields = issueType.Fields; foreach (KeyValuePair <string, JToken> field in issueType.Fields) { string fieldName = field.Key; JToken fieldVaue = field.Value; bool required = fieldVaue["required"].Value <bool>(); if (required && !fieldName.Contains(JiraCustomFieldValue.CUSTOM_FIELD_PREFIX) && !jsonIssueFields.Properties().Any(p => p.Name == fieldName)) { messages.Add(String.Format("JIRA field '{0}' is required for project '{1}' and issue type {2} but was not provided.", fieldName, jiraIssue.Fields.Project.Key, jiraIssue.Fields.IssueType.Id)); } } //Remove any properties that are not listed in the meta-data (never issue-type) List <string> fieldsToRemove = new List <string>(); foreach (KeyValuePair <string, JToken> field in jsonIssueFields) { string fieldName = field.Key; if (issueType.Fields[fieldName] == null && fieldName != "issuetype") { fieldsToRemove.Add(fieldName); //Remove the property } } //Now do the removes foreach (string fieldToRemove in fieldsToRemove) { jsonIssueFields.Remove(fieldToRemove); } } } } //Throw an exception if we have any messages if (messages.Count > 0) { throw new ApplicationException(String.Join(" \n", messages)); } //Now we need to add the custom properties, which are not automatically serialized foreach (JiraCustomFieldValue jiraCustomFieldValue in jiraIssue.CustomFieldValues) { if (jiraCustomFieldValue.Value != null) { int id = jiraCustomFieldValue.CustomFieldId; string fieldName = jiraCustomFieldValue.CustomFieldName; //Make sure JIRA expects this field for this issue type if (issueType != null && issueType.Fields[fieldName] != null) { //Add to the issue json fields, handling the appropriate types correctly switch (jiraCustomFieldValue.Value.CustomPropertyType) { case CustomPropertyValue.CustomPropertyTypeEnum.Boolean: if (jiraCustomFieldValue.Value.BooleanValue.HasValue) { jsonIssueFields.Add(fieldName, jiraCustomFieldValue.Value.BooleanValue.Value); } break; case CustomPropertyValue.CustomPropertyTypeEnum.Date: if (jiraCustomFieldValue.Value.DateTimeValue.HasValue) { jsonIssueFields.Add(fieldName, jiraCustomFieldValue.Value.DateTimeValue.Value); } break; case CustomPropertyValue.CustomPropertyTypeEnum.Decimal: if (jiraCustomFieldValue.Value.DecimalValue.HasValue) { jsonIssueFields.Add(fieldName, jiraCustomFieldValue.Value.DecimalValue.Value); } break; case CustomPropertyValue.CustomPropertyTypeEnum.Integer: if (jiraCustomFieldValue.Value.IntegerValue.HasValue) { jsonIssueFields.Add(fieldName, jiraCustomFieldValue.Value.IntegerValue.Value); } break; case CustomPropertyValue.CustomPropertyTypeEnum.List: { //JIRA expects an object with an 'id' property set if (!String.IsNullOrEmpty(jiraCustomFieldValue.Value.StringValue)) { //Need to lookup the id of the custom field option int?customFieldOptionId = LookupCustomFieldOptionId(jiraCustomFieldValue.Value.StringValue, jIssueTypeFields, jiraCustomFieldValue.CustomFieldName); if (customFieldOptionId.HasValue) { JObject jOption = new JObject(); jOption["id"] = customFieldOptionId.Value.ToString(); jsonIssueFields.Add(fieldName, jOption); } } } break; case CustomPropertyValue.CustomPropertyTypeEnum.MultiList: { //JIRA expects an array of objects with an 'id' property set if (jiraCustomFieldValue.Value.MultiListValue.Count > 0) { JArray jOptions = new JArray(); foreach (string optionName in jiraCustomFieldValue.Value.MultiListValue) { //Need to lookup the id of the custom field option int?customFieldOptionId = LookupCustomFieldOptionId(optionName, jIssueTypeFields, jiraCustomFieldValue.CustomFieldName); if (customFieldOptionId.HasValue) { JObject jOption = new JObject(); jOption["id"] = customFieldOptionId.Value.ToString(); jOptions.Add(jOption); } } jsonIssueFields.Add(fieldName, jOptions); } } break; case CustomPropertyValue.CustomPropertyTypeEnum.Text: { if (!String.IsNullOrEmpty(jiraCustomFieldValue.Value.StringValue)) { jsonIssueFields.Add(fieldName, jiraCustomFieldValue.Value.StringValue); } } break; case CustomPropertyValue.CustomPropertyTypeEnum.User: { //JIRA expects a user object with an 'name' property set if (!String.IsNullOrEmpty(jiraCustomFieldValue.Value.StringValue)) { JObject jUser = new JObject(); jUser["name"] = jiraCustomFieldValue.Value.StringValue; jsonIssueFields.Add(fieldName, jUser); } } break; } } } } string json = JsonConvert.SerializeObject(jsonIssue, Formatting.Indented, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); LogTraceEvent(this.eventLog, json, EventLogEntryType.Information); json = RunQuery(JiraResource.issue, null, json, "POST"); JiraIssueLink jiraIssueLink = JsonConvert.DeserializeObject <JiraIssueLink>(json); return(jiraIssueLink); }