public TfsFailedValidation(Ticket source, ArrayList validationErrors) { Source = source; Issues = new List<TfsIssue>(); Title = ""; Type = ""; foreach (Field field in validationErrors) { Title = field.WorkItem.Title; Type = field.WorkItem.Type.Name; var summary = String.Format("{0} '{1}' flagged an error status of '{2}'.", (field.IsRequired ? "Required field" : "Field"), field.Name, field.Status); var info = new List<string>(); if (field.HasAllowedValuesList) { info.Add("Allowable values: " + toCommaSeparatedString(field.AllowedValues)); } if (field.ProhibitedValues != null && field.ProhibitedValues.Count > 0) { info.Add("Prohibited values: " + toCommaSeparatedString(field.ProhibitedValues)); } Issues.Add(new TfsIssue(field.Name, summary, field.Value, info)); } Summary = String.Format("Found {0} issue{1} with ticket '{2}'.", Issues.Count, (Issues.Count > 1) ? "(s)" : "", Source.ID); }
private void updateWorkItem(Ticket source, WorkItem workItem) { var ticketTitle = workItem.Id + " - " + workItem.Title; onDetailedProcessing(ticketTitle); if (source.HasParent) { var parentWorkItem = findWorkItem(source.Parent); if (parentWorkItem != null) { try { var workItemStore = (WorkItemStore) tfs.GetService(typeof (WorkItemStore)); var linkType = workItemStore.WorkItemLinkTypes[CoreLinkTypeReferenceNames.Hierarchy]; parentWorkItem.Links.Add(new WorkItemLink(linkType.ForwardEnd, workItem.Id)); } catch (Exception linkException) { importSummary.Warnings.Add(string.Format("Failed to update parent link for '{0}'.", ticketTitle)); importSummary.Warnings.Add(linkException.Message); } } } if (source.HasLinks) { var workItemStore = (WorkItemStore) tfs.GetService(typeof (WorkItemStore)); if (workItemStore.WorkItemLinkTypes.Contains("System.LinkTypes.Related")) { var linkType = workItemStore.WorkItemLinkTypes["System.LinkTypes.Related"]; var linkTypeEnd = workItemStore.WorkItemLinkTypes.LinkTypeEnds[linkType.ForwardEnd.Name]; foreach (var link in source.Links) { var linkedWorkItem = findWorkItem(link.LinkedTo); if (linkedWorkItem != null) { try { var relatedLink = new RelatedLink(linkTypeEnd, linkedWorkItem.Id); relatedLink.Comment = link.LinkName; workItem.Links.Add(relatedLink); } catch (Exception linkException) { if (linkException.Message.Contains("TF237099") == false) { importSummary.Warnings.Add(string.Format("Failed to update links for '{0}'.", ticketTitle)); importSummary.Warnings.Add(linkException.Message); } } } } } } if (string.IsNullOrWhiteSpace(source.Epic) == false) { var workItemStore = (WorkItemStore) tfs.GetService(typeof (WorkItemStore)); var feature = findWorkItem(source.Epic); if (feature != null) { try { var linkType = workItemStore.WorkItemLinkTypes["System.LinkTypes.Hierarchy"]; var linkTypeEnd = workItemStore.WorkItemLinkTypes.LinkTypeEnds[linkType.ReverseEnd.Name]; var relatedLink = new RelatedLink(linkTypeEnd, feature.Id); relatedLink.Comment = string.Format("A member of Epic '{0} - {1}'.", feature.Id, feature.Title); workItem.Links.Add(relatedLink); } catch (Exception linkException) { if (linkException.Message.Contains("TF237099") == false) { importSummary.Warnings.Add(string.Format("Failed to update epic link for '{0}'.", ticketTitle)); importSummary.Warnings.Add(linkException.Message); } } } } if (workItem.IsDirty) { try { workItem.Save(SaveFlags.MergeLinks); } catch (Exception ex) { importSummary.Errors.Add( string.Format("Failed to update work item '{0} - {1}'.", workItem.Id, workItem.Title)); importSummary.Errors.Add(ex.Message); return; } } updateWorkItemState(source, workItem); }
private void updateWorkItemState(Ticket source, WorkItem workItem) { var statesTosave = new List<string>(); if (source.TicketState != Ticket.State.Done) { if (source.TicketState == Ticket.State.InProgress) { statesTosave.Add(tfsStateMap.GetSelectedInProgressStateFor(workItem.Type.Name)); } else if (source.TicketState == Ticket.State.Todo || source.TicketState == Ticket.State.Unknown) { statesTosave.Add(tfsStateMap.GetSelectedApprovedStateFor(workItem.Type.Name)); } if (importSummary.OpenTickets.ContainsKey(source.TicketType) == false) { importSummary.OpenTickets.Add(source.TicketType, 1); } else { importSummary.OpenTickets[source.TicketType]++; } } else { statesTosave = tfsStateMap.GetStateTransistionsToDoneFor(workItem.Type.Name); } foreach (var stateToSave in statesTosave) { workItem.State = stateToSave; var validationErrors = workItem.Validate(); if (validationErrors.Count == 0) { workItem.Save(SaveFlags.MergeLinks); } else { var waring = string.Format("Failed to set state for Work-item {0} - \"{1}\"", workItem.Id, workItem.Title); importSummary.Warnings.Add(waring); var failure = new TfsFailedValidation(source, validationErrors); importSummary.Warnings.Add(" " + failure.Summary); foreach (var issue in failure.Issues) { var fieldInTrouble = string.Format(" * {0} - {1} (Value: {2})", issue.Name, issue.Problem, issue.Value); importSummary.Warnings.Add(fieldInTrouble); foreach (var info in issue.Info) { importSummary.Warnings.Add(" * " + info); } } break; } } }
public bool AddTicket(Ticket toAdd) { var addedOk = false; var ticketSummary = toAdd.ID + " - " + toAdd.Summary; onDetailedProcessing(ticketSummary); if (previouslyImported.ContainsKey(toAdd.ID) == false) { var workItem = toWorkItem(toAdd); var rejectedAttachments = new List<string>(); foreach (var attachment in toAdd.Attachments) { if (attachment.Downloaded) { var toAttach = new Microsoft.TeamFoundation.WorkItemTracking.Client.Attachment(attachment.Source); workItem.Attachments.Add(toAttach); } } if (toAdd.Description.Length > max_Description_length) { var attachment = Path.Combine(Path.GetTempPath(), string.Format("{0} (Description).txt", toAdd.ID)); File.WriteAllText(attachment, toAdd.Description); var toAttach = new Microsoft.TeamFoundation.WorkItemTracking.Client.Attachment(attachment); workItem.Attachments.Add(toAttach); } var attempts = workItem.AttachedFileCount + 1; do { try { workItem.Save(SaveFlags.MergeAll); addedOk = true; } catch (FileAttachmentException attachmentException) { var rejectedFile = attachmentException.SourceAttachment.Name; rejectedAttachments.Add(string.Format("{0} ({1})", rejectedFile, attachmentException.Message)); workItem.Attachments.Clear(); foreach (var attachment in toAdd.Attachments) { if (string.CompareOrdinal(rejectedFile, attachment.FileName) != 0 && attachment.Downloaded) { var toAttach = new Microsoft.TeamFoundation.WorkItemTracking.Client.Attachment(attachment.Source); workItem.Attachments.Add(toAttach); } } attempts--; } catch (Exception generalException) { importSummary.Errors.Add(string.Format("Failed to add work item '{0}'.", ticketSummary)); importSummary.Errors.Add(generalException.Message); break; } } while (addedOk == false && attempts > 0); if (addedOk) { newlyImported[toAdd] = workItem; if (toAdd.Description.Length > max_Description_length) { importSummary.Warnings.Add( string.Format("Description for {0} - \"{1}\" stored as attachment. Exceeded 32k.", workItem.Id, workItem.Title)); } if (rejectedAttachments.Count > 0) { importSummary.Warnings.Add( string.Format("Failed to attach the following item(s) to {0} - \"{1}\"", workItem.Id, workItem.Title)); foreach (var file in rejectedAttachments) { importSummary.Warnings.Add(string.Format(" - {0}", file)); } failedAttachments = true; } } } else { addedOk = true; } return addedOk; }
public bool CheckTicket(Ticket toAdd, out IFailedTicket failure) { failure = null; var okToAdd = true; if (previouslyImported.ContainsKey(toAdd.ID) == false) { var validationErrors = toWorkItem(toAdd).Validate(); okToAdd = (validationErrors.Count == 0); if (okToAdd == false) { failure = new TfsFailedValidation(toAdd, validationErrors); } } return okToAdd; }
private WorkItem toWorkItem(Ticket toImport) { var tfs_impersonated = tfsUsers.ImpersonateDefaultCreator(); if (tfsUsers.CanAddTicket(toImport.CreatedBy)) { tfs_impersonated = tfsUsers.Impersonate(toImport.CreatedBy); } var workItemStore = (WorkItemStore) tfs_impersonated.GetService(typeof (WorkItemStore)); var workItemTypes = workItemStore.Projects[project].WorkItemTypes; var workItemType = workItemTypes[toImport.TicketType]; var workItem = new WorkItem(workItemType); foreach (var fieldName in tfsFieldMap.Fields.EditableFields. Where(fieldName => string.IsNullOrEmpty(fields[fieldName].DefaultValue) == false)) { assignToField(workItem, fieldName, fields[fieldName].DefaultValue); } workItem.Title = toImport.Summary; var description = toImport.Description; // TFS's limit on HTML / PlainText fields is 32k. if (description.Length > max_Description_length) { var attachment = string.Format("{0} (Description).txt", toImport.ID); description = "<p><b>Description stored as Attachment</b></p>"; description += "<ul><li>Description exceeds 32K.A limit imposed by TFS.</li>"; description += ("<li>See attachment \"" + attachment + "\"</li></ul>"); } workItem.Description = description; assignToField(workItem, "Repro Steps", description); assignToField(workItem, "Team", assignedTeam); tfsUsers.AssignUser(toImport.AssignedTo, workItem); assignToField(workItem, "Story Points", toImport.StoryPoints); assignToField(workItem, "Effort", toImport.StoryPoints); workItem.AreaPath = (string.IsNullOrWhiteSpace(assignedAreaPath) ? project : assignedAreaPath); assignToField(workItem, "External Reference", toImport.ID); assignToField(workItem, tfsPriorityMap.PriorityField, tfsPriorityMap[toImport.Priority]); if (toImport.HasUrl) { try { var hl = new Hyperlink(toImport.Url) { Comment = string.Format("{0} [{1}]", externalReferenceTag, toImport.ID) }; workItem.Links.Add(hl); } catch { /*Do nothing..*/ } } var c = new StringBuilder(); foreach (var comment in toImport.Comments) { var body = String.Format("<i>{0}</i></br>Created by {1} on the {2}.<br>", comment.Body.Replace(Environment.NewLine, "<br>"), comment.Author.DisplayName, comment.CreatedOn.ToShortDateString()); if (comment.UpdatedLater) { body = String.Format("{0}<br>(Last updated on the {1}).<br>", body, comment.Updated.ToShortDateString()); } c.Append(body); } if (c.Length > 0) { c.Append("<br>"); } c.Append(string.Format("<u><b>Additional {0} information</b></u><br>", externalReferenceTag)); var rows = new List<Tuple<string, string>> { new Tuple<string, string>("Ticket", string.Format("<a href=\"{0}\">{1}</a>", toImport.Url, toImport.ID + " - " + toImport.Summary)), new Tuple<string, string>("Created by ", toImport.CreatedBy.DisplayName), new Tuple<string, string>("Created on ", toImport.CreatedOn.ToString(CultureInfo.InvariantCulture)) }; if (toImport.TicketState == Ticket.State.Done) { rows.Add(new Tuple<string, string>("Closed on ", toImport.ClosedOn.ToString(CultureInfo.InvariantCulture))); } if (string.IsNullOrWhiteSpace(toImport.Project) == false) { rows.Add(new Tuple<string, string>("Belonged To", toImport.Project)); } c.Append("<table style=\"width:100%\">"); foreach (var row in rows) { c.Append(string.Format("<tr><td><b>{0}</b></td><td>{1}</td></tr>", row.Item1, row.Item2)); } c.Append("</table>"); workItem.History = c.ToString(); return workItem; }
public IEnumerable<Ticket> Tickets(IAvailableTicketTypes availableTypes) { var map = new JiraTypeMap(this, availableTypes); foreach (var jiraKey in jira.EnumerateIssues(jiraProject)) { var issueRef = new IssueRef { id = jiraKey.id, key = jiraKey.key }; var jiraTicket = jira.LoadIssue(issueRef); onDetailedProcessing(jiraTicket.key + " - " + jiraTicket.fields.summary); var ticket = new Ticket(); ticket.TicketType = map[jiraTicket.fields.issuetype.name]; ticket.ID = jiraTicket.key; ticket.Summary = JiraString.StripNonPrintable(jiraTicket.fields.summary); var status = jiraTicket.fields.status.statusCategory.key.ToUpper(); switch (status) { case "NEW": ticket.TicketState = Ticket.State.Todo; break; case "DONE": ticket.TicketState = Ticket.State.Done; break; case "INDETERMINATE": ticket.TicketState = Ticket.State.InProgress; break; default: ticket.TicketState = Ticket.State.Unknown; break; } ticket.Parent = jiraTicket.fields.parent.key; ticket.Description = JiraString.StripNonPrintable(jiraTicket.fields.description); if (PreferHtml && string.IsNullOrWhiteSpace(jiraTicket.renderedFields.description) == false) { ticket.Description = JiraString.StripNonPrintable(jiraTicket.renderedFields.description); } ticket.CreatedOn = jiraTicket.fields.created; ticket.LastModified = jiraTicket.fields.updated; ticket.CreatedBy = new User(jiraTicket.fields.reporter.displayName, jiraTicket.fields.reporter.name, jiraTicket.fields.reporter.emailAddress); ticket.AssignedTo = new User(jiraTicket.fields.assignee.displayName, jiraTicket.fields.assignee.name, jiraTicket.fields.assignee.emailAddress); ticket.Epic = jiraTicket.fields.customfield_10800; ticket.ExternalReference = jiraTicket.key; ticket.Url = jiraServer + "/browse/" + jiraTicket.key; int.TryParse(jiraTicket.fields.customfield_10004, out ticket.StoryPoints); foreach (var link in jiraTicket.fields.issuelinks) { if (string.Compare(link.inwardIssue.key, jiraTicket.key) != 0) { ticket.Links.Add(new Link(link.inwardIssue.key, link.type.name)); } if (string.Compare(link.outwardIssue.key, jiraTicket.key) != 0) { ticket.Links.Add(new Link(link.outwardIssue.key, link.type.name)); } } foreach (var jiraComment in jiraTicket.fields.comments) { var author = new User(jiraComment.author.displayName, jiraComment.author.name, jiraComment.author.emailAddress); var comment = new Comment(author, jiraComment.body, jiraComment.created); if (jiraComment.updated.Date > jiraComment.created.Date) { comment.Updated = jiraComment.updated; } ticket.Comments.Add(comment); } foreach (var attachment in jiraTicket.fields.attachment) { ticket.Attachments.Add(new Attachment(attachment.filename, attachment.content)); } ticket.Priority = jiraTicket.fields.priority.name; ticket.Project = jiraTicket.fields.project.name; if (jiraTicket.fields.resolutiondate != null) { ticket.ClosedOn = jiraTicket.fields.resolutiondate; } yield return (ticket); } }
public void DownloadAttachments(Ticket ticket, string downloadFolder) { using (var webClient = new WebClient()) { var downloaded = new Dictionary<string, int>(); foreach (var attachment in ticket.Attachments) { onDetailedProcessing("Downloading " + attachment.FileName); var sourceUri = string.Format("{0}?&os_username={1}&os_password={2}", attachment.Source, userName, password); string name = Path.GetFileNameWithoutExtension(attachment.FileName), extension = Path.GetExtension(attachment.FileName), downloadedName = attachment.FileName; // Jira can have more than one of the same file attached to a ticket ... var nextCopy = 0; if (downloaded.ContainsKey(attachment.FileName)) { nextCopy = downloaded[attachment.FileName]; downloadedName = string.Format("{0}_{1}{2}", name, nextCopy, extension); } downloaded[attachment.FileName] = ++nextCopy; try { var downloadTo = Path.Combine(downloadFolder, downloadedName); webClient.DownloadFile(new Uri(sourceUri), downloadTo); attachment.Source = downloadTo; attachment.FileName = downloadedName; attachment.Downloaded = true; } catch { attachment.Downloaded = false; } } } }
private void updateWorkItem(Ticket source, WorkItem workItem) { var ticketTitle = workItem.Id + " - " + workItem.Title; onDetailedProcessing(ticketTitle); if (source.HasParent) { var parentWorkItem = findWorkItem(source.Parent); if (parentWorkItem != null) { try { var workItemStore = (WorkItemStore)tfs.GetService(typeof(WorkItemStore)); var linkType = workItemStore.WorkItemLinkTypes[CoreLinkTypeReferenceNames.Hierarchy]; parentWorkItem.Links.Add(new WorkItemLink(linkType.ForwardEnd, workItem.Id)); } catch (Exception linkException) { importSummary.Warnings.Add(string.Format("Failed to update parent link for '{0}'.", ticketTitle)); importSummary.Warnings.Add(linkException.Message); } } } if (source.HasLinks) { var workItemStore = (WorkItemStore)tfs.GetService(typeof(WorkItemStore)); if (workItemStore.WorkItemLinkTypes.Contains("System.LinkTypes.Related")) { var linkType = workItemStore.WorkItemLinkTypes["System.LinkTypes.Related"]; var linkTypeEnd = workItemStore.WorkItemLinkTypes.LinkTypeEnds[linkType.ForwardEnd.Name]; foreach (var link in source.Links) { var linkedWorkItem = findWorkItem(link.LinkedTo); if (linkedWorkItem != null) { try { var relatedLink = new RelatedLink(linkTypeEnd, linkedWorkItem.Id); relatedLink.Comment = link.LinkName; workItem.Links.Add(relatedLink); } catch (Exception linkException) { if (linkException.Message.Contains("TF237099") == false) { importSummary.Warnings.Add(string.Format("Failed to update links for '{0}'.", ticketTitle)); importSummary.Warnings.Add(linkException.Message); } } } } } } if (string.IsNullOrWhiteSpace(source.Epic) == false) { var workItemStore = (WorkItemStore)tfs.GetService(typeof(WorkItemStore)); var feature = findWorkItem(source.Epic); if (feature != null) { try { var linkType = workItemStore.WorkItemLinkTypes["System.LinkTypes.Hierarchy"]; var linkTypeEnd = workItemStore.WorkItemLinkTypes.LinkTypeEnds[linkType.ReverseEnd.Name]; var relatedLink = new RelatedLink(linkTypeEnd, feature.Id); relatedLink.Comment = string.Format("A member of Epic '{0} - {1}'.", feature.Id, feature.Title); workItem.Links.Add(relatedLink); } catch (Exception linkException) { if (linkException.Message.Contains("TF237099") == false) { importSummary.Warnings.Add(string.Format("Failed to update epic link for '{0}'.", ticketTitle)); importSummary.Warnings.Add(linkException.Message); } } } } if (workItem.IsDirty) { try { workItem.Save(SaveFlags.MergeLinks); } catch (Exception ex) { importSummary.Errors.Add( string.Format("Failed to update work item '{0} - {1}'.", workItem.Id, workItem.Title)); importSummary.Errors.Add(ex.Message); return; } } updateWorkItemState(source, workItem); }
public bool AddTicket(Ticket toAdd) { var addedOk = false; var ticketSummary = toAdd.ID + " - " + toAdd.Summary; onDetailedProcessing(ticketSummary); if (previouslyImported.ContainsKey(toAdd.ID) == false) { var workItem = toWorkItem(toAdd); var rejectedAttachments = new List <string>(); foreach (var attachment in toAdd.Attachments) { if (attachment.Downloaded) { var toAttach = new Microsoft.TeamFoundation.WorkItemTracking.Client.Attachment(attachment.Source); workItem.Attachments.Add(toAttach); } } if (toAdd.Description.Length > max_Description_length) { var attachment = Path.Combine(Path.GetTempPath(), string.Format("{0} (Description).txt", toAdd.ID)); File.WriteAllText(attachment, toAdd.Description); var toAttach = new Microsoft.TeamFoundation.WorkItemTracking.Client.Attachment(attachment); workItem.Attachments.Add(toAttach); } var attempts = workItem.AttachedFileCount + 1; do { try { workItem.Save(SaveFlags.MergeAll); addedOk = true; } catch (FileAttachmentException attachmentException) { var rejectedFile = attachmentException.SourceAttachment.Name; rejectedAttachments.Add(string.Format("{0} ({1})", rejectedFile, attachmentException.Message)); workItem.Attachments.Clear(); foreach (var attachment in toAdd.Attachments) { if (string.CompareOrdinal(rejectedFile, attachment.FileName) != 0 && attachment.Downloaded) { var toAttach = new Microsoft.TeamFoundation.WorkItemTracking.Client.Attachment(attachment.Source); workItem.Attachments.Add(toAttach); } } attempts--; } catch (Exception generalException) { importSummary.Errors.Add(string.Format("Failed to add work item '{0}'.", ticketSummary)); importSummary.Errors.Add(generalException.Message); break; } } while (addedOk == false && attempts > 0); if (addedOk) { newlyImported[toAdd] = workItem; if (toAdd.Description.Length > max_Description_length) { importSummary.Warnings.Add( string.Format("Description for {0} - \"{1}\" stored as attachment. Exceeded 32k.", workItem.Id, workItem.Title)); } if (rejectedAttachments.Count > 0) { importSummary.Warnings.Add( string.Format("Failed to attach the following item(s) to {0} - \"{1}\"", workItem.Id, workItem.Title)); foreach (var file in rejectedAttachments) { importSummary.Warnings.Add(string.Format(" - {0}", file)); } failedAttachments = true; } } } else { addedOk = true; } return(addedOk); }
private WorkItem toWorkItem(Ticket toImport) { var tfs_impersonated = tfsUsers.ImpersonateDefaultCreator(); if (tfsUsers.CanAddTicket(toImport.CreatedBy)) { tfs_impersonated = tfsUsers.Impersonate(toImport.CreatedBy); } var workItemStore = (WorkItemStore)tfs_impersonated.GetService(typeof(WorkItemStore)); var workItemTypes = workItemStore.Projects[project].WorkItemTypes; var workItemType = workItemTypes[toImport.TicketType]; var workItem = new WorkItem(workItemType); foreach (var fieldName in tfsFieldMap.Fields.EditableFields. Where(fieldName => string.IsNullOrEmpty(fields[fieldName].DefaultValue) == false)) { assignToField(workItem, fieldName, fields[fieldName].DefaultValue); } workItem.Title = toImport.Summary; var description = toImport.Description; // TFS's limit on HTML / PlainText fields is 32k. if (description.Length > max_Description_length) { var attachment = string.Format("{0} (Description).txt", toImport.ID); description = "<p><b>Description stored as Attachment</b></p>"; description += "<ul><li>Description exceeds 32K.A limit imposed by TFS.</li>"; description += ("<li>See attachment \"" + attachment + "\"</li></ul>"); } workItem.Description = description; assignToField(workItem, "Repro Steps", description); assignToField(workItem, "Team", assignedTeam); tfsUsers.AssignUser(toImport.AssignedTo, workItem); assignToField(workItem, "Story Points", toImport.StoryPoints); assignToField(workItem, "Effort", toImport.StoryPoints); workItem.AreaPath = (string.IsNullOrWhiteSpace(assignedAreaPath) ? project : assignedAreaPath); assignToField(workItem, "External Reference", toImport.ID); assignToField(workItem, tfsPriorityMap.PriorityField, tfsPriorityMap[toImport.Priority]); if (toImport.HasUrl) { try { var hl = new Hyperlink(toImport.Url) { Comment = string.Format("{0} [{1}]", externalReferenceTag, toImport.ID) }; workItem.Links.Add(hl); } catch { /*Do nothing..*/ } } var c = new StringBuilder(); foreach (var comment in toImport.Comments) { var body = String.Format("<i>{0}</i></br>Created by {1} on the {2}.<br>", comment.Body.Replace(Environment.NewLine, "<br>"), comment.Author.DisplayName, comment.CreatedOn.ToShortDateString()); if (comment.UpdatedLater) { body = String.Format("{0}<br>(Last updated on the {1}).<br>", body, comment.Updated.ToShortDateString()); } c.Append(body); } if (c.Length > 0) { c.Append("<br>"); } c.Append(string.Format("<u><b>Additional {0} information</b></u><br>", externalReferenceTag)); var rows = new List <Tuple <string, string> > { new Tuple <string, string>("Ticket", string.Format("<a href=\"{0}\">{1}</a>", toImport.Url, toImport.ID + " - " + toImport.Summary)), new Tuple <string, string>("Created by ", toImport.CreatedBy.DisplayName), new Tuple <string, string>("Created on ", toImport.CreatedOn.ToString(CultureInfo.InvariantCulture)) }; if (toImport.TicketState == Ticket.State.Done) { rows.Add(new Tuple <string, string>("Closed on ", toImport.ClosedOn.ToString(CultureInfo.InvariantCulture))); } if (string.IsNullOrWhiteSpace(toImport.Project) == false) { rows.Add(new Tuple <string, string>("Belonged To", toImport.Project)); } c.Append("<table style=\"width:100%\">"); foreach (var row in rows) { c.Append(string.Format("<tr><td><b>{0}</b></td><td>{1}</td></tr>", row.Item1, row.Item2)); } c.Append("</table>"); workItem.History = c.ToString(); return(workItem); }