using Microsoft.TeamFoundation.Client; using Microsoft.TeamFoundation.Core.WebApi; using Microsoft.TeamFoundation.WorkItemTracking.Client; using Microsoft.TeamFoundation.WorkItemTracking.Proxy; using Microsoft.TeamFoundation.WorkItemTracking.WebApi; using Microsoft.VisualStudio.Services.Common; using Microsoft.VisualStudio.Services.WebApi; using Microsoft.VisualStudio.Services.WebApi.Patch; using Microsoft.VisualStudio.Services.WebApi.Patch.Json; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Tango.Core; namespace Tango.TFS { /// /// Represents a VSTS (Visual Studio On-line) client implementation. /// /// public class TeamFoundationServiceClient : ExtendedObject, ITeamFoundationServiceClient { #region Extension Fields Constants private class ExtensionFields { public const String FOUND_IN_BUILD = "Microsoft.VSTS.Build.FoundIn"; public const String SEVERITY = "Microsoft.VSTS.Common.Severity"; public const String PRIORITY = "Microsoft.VSTS.Common.Priority"; public const String STEPS_TO_REP = "Microsoft.VSTS.TCM.ReproSteps"; public const String SYSTEM_INFO = "Microsoft.VSTS.TCM.SystemInfo"; public const String RESOLVED_BY = "Microsoft.VSTS.Common.ResolvedBy"; public const String RESOLVED_DATE = "Microsoft.VSTS.Common.ResolvedDate"; public const String RESOLVED_REASON = "Microsoft.VSTS.Common.ResolvedReason"; public const String EMBEDDED_VERSION = "Custom.EmbeddedVersion"; public const String MACHINE_SN = "Custom.MachineSN"; public const String ENVIRONMENT = "Custom.Environment"; public const String LOGGED_IN_USER = "Custom.LoggedInUser"; public const String MACHINE_TYPE = "Custom.MachineType"; } #endregion #region Properties /// /// Gets the default team collection URL. /// public String CollectionURL { get; private set; } /// /// Gets the personal token used to authenticate against the service. https://docs.microsoft.com/en-us/vsts/accounts/use-personal-access-tokens-to-authenticate?view=vsts /// public String PersonalToken { get; private set; } /// /// Gets the VSTS user name (optional). /// public String UserName { get; private set; } #endregion #region Constructors /// /// Initializes a new instance of the class. /// /// The collection URL. /// Name of the user. /// The personal token. public TeamFoundationServiceClient(String collectionURL, String userName, String personalToken) { CollectionURL = collectionURL; UserName = userName; PersonalToken = personalToken; } #endregion #region Public Methods /// /// Gets a team project by the specified name. /// /// The name. /// public Task GetProject(string name) { return Task.Factory.StartNew(() => { Project p = new Project(); VssConnection connection = CreateConnection(); LogManager.Log("Retrieving project " + name + " details..."); ProjectHttpClient projectClient = connection.GetClient(); TeamProjectReference project = projectClient.GetProjects().Result.FirstOrDefault(x => x.Name == name); if (project == null) { throw LogManager.Log(new ArgumentException(String.Format("Project '{0}' could not be found.", name))); } LogManager.Log("Project details successfully retrieved."); p.Name = project.Name; p.ID = project.Id; p.URL = project.Url; p.Description = project.Description; TeamHttpClient teamClient = connection.GetClient(); var team = teamClient.GetTeamsAsync(project.Id.ToString()).Result.FirstOrDefault(); IEnumerable teamMembers = teamClient.GetTeamMembersAsync(project.Id.ToString(), team.Id.ToString()).Result; foreach (var member in teamMembers) { TeamMember teamMember = new TeamMember(); teamMember.ID = member.Id; teamMember.DisplayName = member.DisplayName; teamMember.ImageURL = member.ImageUrl; teamMember.UniqueName = member.UniqueName; teamMember.URL = member.Url; p.Members.Add(teamMember); } TaggingHttpClient taggingClient = connection.GetClient(); WebApiTagDefinitionList tags = taggingClient.GetTagsAsync(project.Id).Result; foreach (var tag in tags) { p.Tags.Add(new Tag() { ID = tag.Id, Name = tag.Name, }); } var projCollection = new TfsTeamProjectCollection(new Uri(CollectionURL), connection.Credentials); var store = projCollection.GetService(); WorkItemCollection queryResults = store.Query("Select [State], [Title] " + "From WorkItems " + "Where [Work Item Type] = 'User Story'"); var userStories = queryResults.OfType().Where(x => x.Project.Name == project.Name).ToList(); foreach (var userStory in userStories) { WorkItem story = new WorkItem(); story.Type = WorkItemType.UserStory; story.Title = userStory.Title; story.ID = userStory.Id; story.URL = userStory.Uri.ToString(); story.Description = userStory.Description; if (userStory.Fields[CoreField.AssignedTo].Value != null) { story.AssignedTo = p.Members.SingleOrDefault(x => x.AssignName == userStory.Fields[CoreField.AssignedTo].Value.ToString()); } story.Area = new Area() { Name = userStory.AreaPath }; story.Iteration = new Iteration() { Name = userStory.IterationPath }; p.UserStories.Add(story); } var mainArea = new Area() { Name = project.Name, Path = project.Name }; p.Areas.Add(mainArea); foreach (var area in store.Projects[project.Name].AreaRootNodes.OfType()) { Area a = new Area(); FillArea(a, area); mainArea.SubAreas.Add(a); } foreach (var iteration in store.Projects[project.Name].IterationRootNodes.OfType()) { p.Iterations.Add(new Iteration() { Name = iteration.Name, Path = iteration.Path, }); } return p; }); } private Area FillArea(Area area, Node node) { area.Name = node.Name; area.Path = node.Path; foreach (var child in node.ChildNodes.OfType()) { var subArea = new Area(); FillArea(subArea, child); area.SubAreas.Add(subArea); } return area; } /// /// Uploads a work item to the specified team project. /// /// The project. /// The work item. /// public Task UploadWorkItem(Project project, WorkItem workItem) { return Task.Factory.StartNew(() => { var connection = CreateConnection(); WorkItemTrackingHttpClient witClient = connection.GetClient(); var patchDocument = new JsonPatchDocument(); patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetFieldNameForWrite(CoreField.Title), Value = workItem.Title, }); if (!String.IsNullOrWhiteSpace(workItem.Description)) { patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetFieldNameForWrite(CoreField.Description), Value = workItem.Description, }); } if (!String.IsNullOrWhiteSpace(workItem.Comment)) { patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetFieldNameForWrite(CoreField.History), Value = workItem.Comment, From = workItem.CreatedBy.AssignName }); } if (workItem.Iteration != null) { patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetFieldNameForWrite(CoreField.IterationPath), Value = workItem.Iteration.Path, }); } patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetFieldNameForWrite(CoreField.AreaPath), Value = workItem.Area.Path, }); patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetFieldNameForWrite(CoreField.AssignedTo), Value = workItem.AssignedTo.AssignName, }); patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetFieldNameForWrite(CoreField.CreatedBy), Value = workItem.CreatedBy.AssignName, }); patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetFieldNameForWrite(CoreField.ChangedBy), Value = workItem.ChangedBy.AssignName, }); patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetFieldNameForWrite(CoreField.AuthorizedAs), Value = workItem.AuthorizedAs.AssignName, }); if (workItem.UserStory != null) { patchDocument.Add( new JsonPatchOperation() { Operation = Operation.Add, Path = "/relations/-", Value = new { rel = "System.LinkTypes.Hierarchy-Reverse", url = workItem.UserStory.URL, } } ); } foreach (var attachment in workItem.Attachments) { Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.AttachmentReference attachmentReference = witClient.CreateAttachmentAsync(attachment.FilePath).Result; patchDocument.Add( new JsonPatchOperation() { Operation = Operation.Add, Path = "/relations/-", Value = new { rel = "AttachedFile", url = attachmentReference.Url, attributes = new { comment = attachment.Description, name = attachment.Name } } } ); } if (workItem.Tags.Count > 0) { patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetFieldNameForWrite(CoreField.Tags), Value = String.Join("; ", workItem.Tags.Select(x => x.Name)), }); } if (!String.IsNullOrWhiteSpace(workItem.StepsToReproduce)) { patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetExtensionFieldNameForWrite(ExtensionFields.STEPS_TO_REP), Value = workItem.StepsToReproduce, }); } if (!String.IsNullOrWhiteSpace(workItem.FoundInBuild)) { patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetExtensionFieldNameForWrite(ExtensionFields.FOUND_IN_BUILD), Value = workItem.FoundInBuild, }); } patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetFieldNameForWrite(CoreField.State), Value = workItem.State.ToString(), }); patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetExtensionFieldNameForWrite(ExtensionFields.SEVERITY), Value = workItem.Severity.ToDescription(), }); patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetExtensionFieldNameForWrite(ExtensionFields.PRIORITY), Value = (int)workItem.Priority, }); patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetExtensionFieldNameForWrite(ExtensionFields.SYSTEM_INFO), Value = workItem.SystemInformation, }); if (workItem.EmbeddedVersion != null) { patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetExtensionFieldNameForWrite(ExtensionFields.EMBEDDED_VERSION), Value = workItem.EmbeddedVersion, }); } if (workItem.Environment != null) { patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetExtensionFieldNameForWrite(ExtensionFields.ENVIRONMENT), Value = workItem.Environment, }); } if (workItem.MachineSerialNumber != null) { patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetExtensionFieldNameForWrite(ExtensionFields.MACHINE_SN), Value = workItem.MachineSerialNumber, }); } if (workItem.LoggedInUser != null) { patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetExtensionFieldNameForWrite(ExtensionFields.LOGGED_IN_USER), Value = workItem.LoggedInUser, }); } patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetExtensionFieldNameForWrite(ExtensionFields.MACHINE_TYPE), Value = workItem.MachineType.ToDescription(), }); var resultWorkItem = witClient.CreateWorkItemAsync(patchDocument, project.Name, workItem.Type.ToString(), bypassRules: true).Result; workItem.ID = resultWorkItem.Id.Value; workItem.URL = resultWorkItem.Url; return workItem; }); } /// /// Gets a work item by the specified team project and work item id. /// /// The project. /// The identifier. /// public Task GetWorkItem(Project project, int id) { return Task.Factory.StartNew(() => { var connection = CreateConnection(); WorkItemTrackingHttpClient witClient = connection.GetClient(); var item = witClient.GetWorkItemAsync(id, expand: Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItemExpand.All).Result; return ConvertToWorkItem(project, item); }); } /// /// Gets the type of the work items by. /// /// The project. /// The type. /// public Task> GetWorkItemsByType(Project project, WorkItemType type) { return Task.Factory.StartNew>(() => { var connection = CreateConnection(); var projCollection = new TfsTeamProjectCollection(new Uri(CollectionURL), connection.Credentials); var store = projCollection.GetService(); WorkItemCollection queryResults = store.Query(String.Format("Select [Id]" + "From WorkItems " + "Where [Work Item Type] = '{0}'", type.ToString())); var ids = queryResults.OfType().Where(x => x.Project.Name == project.Name).ToList().Select(x => x.Id).ToList(); if (ids.Count == 0) return new List(); WorkItemTrackingHttpClient witClient = connection.GetClient(); var items = witClient.GetWorkItemsAsync(ids).Result; return items.Select(x => ConvertToWorkItem(project, x)).ToList(); }); } /// /// Gets all work items of the specified project. /// /// The project. /// public Task> GetAllWorkItems(Project project) { return Task.Factory.StartNew>(() => { var connection = CreateConnection(); var projCollection = new TfsTeamProjectCollection(new Uri(CollectionURL), connection.Credentials); var store = projCollection.GetService(); WorkItemCollection queryResults = store.Query("Select [Id]" + "From WorkItems " + "Where [Work Item Type] = 'Bug' Or [Work Item Type] = 'Task'"); var ids = queryResults.OfType().Where(x => x.Project.Name == project.Name).ToList().Select(x => x.Id).ToList(); if (ids.Count == 0) return new List(); WorkItemTrackingHttpClient witClient = connection.GetClient(); var items = witClient.GetWorkItemsAsync(ids).Result; return items.Select(x => ConvertToWorkItem(project, x)).ToList(); }); } /// /// Deletes the specified work item. /// /// The project. /// The identifier. /// public Task DeleteWorkItem(Project project, int id) { return Task.Factory.StartNew(() => { var connection = CreateConnection(); WorkItemTrackingHttpClient witClient = connection.GetClient(); var result = witClient.DeleteWorkItemAsync(id, true).Result; }); } /// /// Gets all the work items created by the specified team member. /// /// The project. /// The member. /// public Task> GetWorkItemsCreatedBy(Project project, TeamMember member) { return Task.Factory.StartNew>(() => { var connection = CreateConnection(); var projCollection = new TfsTeamProjectCollection(new Uri(CollectionURL), connection.Credentials); var store = projCollection.GetService(); WorkItemCollection queryResults = store.Query(String.Format("Select [Id]" + "From WorkItems " + "Where [Created By] = '{0}' And [Work Item Type] = 'Bug'", member.AssignName)); var ids = queryResults.OfType().Where(x => x.Project.Name == project.Name).ToList().Select(x => x.Id).ToList(); if (ids.Count == 0) return new List(); WorkItemTrackingHttpClient witClient = connection.GetClient(); var idsList = ids.ChunkBy(200); List items = new List(); foreach (var idsChunk in idsList) { var chunkItems = witClient.GetWorkItemsAsync(idsChunk).Result; items.AddRange(chunkItems); } return items.Select(x => ConvertToWorkItem(project, x)).ToList(); }); } public Task> GetWorkItemAttachements(int id) { return Task.Factory.StartNew>(() => { List handlers = new List(); var connection = CreateConnection(); var projCollection = new TfsTeamProjectCollection(new Uri(CollectionURL), connection.Credentials); projCollection.EnsureAuthenticated(); WorkItemStore wistore = projCollection.GetService(); Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem wi = wistore.GetWorkItem(id); //WorkItemServer wiserver = projCollection.GetService(); foreach (Microsoft.TeamFoundation.WorkItemTracking.Client.Attachment attachement in wi.Attachments) { AttachementHandler handler = new AttachementHandler((targetPath) => { WorkItemTrackingHttpClient witClient = connection.GetClient(); //Method 1. Use for progress... //Stream st = witClient.GetAttachmentContentAsync(Guid.Parse(attachement.FileGuid)).Result; //var tempFile = TemporaryManager.CreateFile(); //using (var fileStream = File.Open(tempFile, FileMode.Create)) //{ // byte[] buffer = new byte[2048]; // read in chunks of 2KB // int bytesRead; // while ((bytesRead = st.Read(buffer, 0, buffer.Length)) > 0) // { // fileStream.Write(buffer, 0, bytesRead); // } //} //File.Copy(tempFile, targetPath, true); //tempFile.Delete(); //Method 2. More basic... WorkItemServer wiserver = projCollection.GetService(); string tempPath = wiserver.DownloadFile(attachement.Id); File.Copy(tempPath, targetPath, true); try { File.Delete(tempPath); } catch { } }); handler.Name = attachement.Name; handler.Comment = attachement.Comment; handler.AttachedTimeUtc = attachement.AttachedTimeUtc; handler.Extension = attachement.Extension; handler.FileGuid = attachement.FileGuid; handler.Length = attachement.Length; handlers.Add(handler); } return handlers; }); } public Task> GetWorkItemsForMachine(Project project, String serialNumber, DateTime startUtc, DateTime endUtc) { return Task.Factory.StartNew>(() => { var connection = CreateConnection(); var projCollection = new TfsTeamProjectCollection(new Uri(CollectionURL), connection.Credentials); var store = projCollection.GetService(); WorkItemCollection queryResults = store.Query($"Select [Id] From WorkItems Where {ExtensionFields.MACHINE_SN} = '{serialNumber}' And [Work Item Type] = 'Bug' AND [Created Date] >= '{startUtc.ToString("MM/dd/yyyy")}' AND [Created Date] <= '{endUtc.ToString("MM/dd/yyyy")}'"); var ids = queryResults.OfType().Where(x => x.Project.Name == project.Name).ToList().Select(x => x.Id).ToList(); if (ids.Count == 0) return new List(); WorkItemTrackingHttpClient witClient = connection.GetClient(); var items = witClient.GetWorkItemsAsync(ids).Result; return items.Select(x => ConvertToWorkItem(project, x)).ToList(); }); } /// /// Sets the state of the work item. /// /// The project. /// The item. /// The state. /// public Task SetWorkItemState(Project project, WorkItem item, State state) { return Task.Factory.StartNew(() => { var connection = CreateConnection(); WorkItemTrackingHttpClient witClient = connection.GetClient(); var patchDocument = new JsonPatchDocument(); patchDocument.Add(new JsonPatchOperation { Operation = Operation.Replace, Path = GetFieldNameForWrite(CoreField.State), Value = state.ToString(), }); var updatedItem = witClient.UpdateWorkItemAsync(patchDocument, item.ID).Result; return ConvertToWorkItem(project, updatedItem); }); } public Task UpdateWorkItemSystemInfo(Project project, WorkItem item, String systemInfo) { return Task.Factory.StartNew(() => { var connection = CreateConnection(); WorkItemTrackingHttpClient witClient = connection.GetClient(); var patchDocument = new JsonPatchDocument(); patchDocument.Add(new JsonPatchOperation { Operation = Operation.Replace, Path = GetExtensionFieldNameForWrite(ExtensionFields.SYSTEM_INFO), Value = systemInfo, }); var updatedItem = witClient.UpdateWorkItemAsync(patchDocument, item.ID).Result; return ConvertToWorkItem(project, updatedItem); }); } /// /// Adds a comment to the work item discussion. /// /// The project. /// The item. /// Team member /// The comment. /// public Task AddWorkItemComment(Project project, WorkItem item, TeamMember teamMember, String comment) { return Task.Factory.StartNew(() => { var connection = CreateConnection(); WorkItemTrackingHttpClient witClient = connection.GetClient(); var patchDocument = new JsonPatchDocument(); patchDocument.Add(new JsonPatchOperation { Operation = Operation.Add, Path = GetFieldNameForWrite(CoreField.History), Value = comment, From = teamMember.AssignName }); var updatedItem = witClient.UpdateWorkItemAsync(patchDocument, item.ID).Result; return ConvertToWorkItem(project, updatedItem); }); } /// /// Sets the work item assignment. /// /// The project. /// The item. /// The member. /// public Task SetWorkItemAssignment(Project project, WorkItem item, TeamMember member) { return Task.Factory.StartNew(() => { var connection = CreateConnection(); WorkItemTrackingHttpClient witClient = connection.GetClient(); var patchDocument = new JsonPatchDocument(); patchDocument.Add(new JsonPatchOperation { Operation = Operation.Replace, Path = GetFieldNameForWrite(CoreField.AssignedTo), Value = member.AssignName, }); var updatedItem = witClient.UpdateWorkItemAsync(patchDocument, item.ID).Result; return ConvertToWorkItem(project, updatedItem); }); } #endregion #region Private Methods /// /// Creates connection to VSTS. /// /// private VssConnection CreateConnection() { LogManager.Log("Generating VSTS connection using personal token " + PersonalToken); VssConnection connection = new VssConnection(new Uri(CollectionURL), new VssBasicCredential(String.Empty, PersonalToken)); connection.Credentials.PromptType = CredentialPromptType.DoNotPrompt; connection.ConnectAsync(VssConnectMode.User).SyncResult(); LogManager.Log("VSS Connection established..."); LogManager.Log("Authenticated: " + connection.HasAuthenticated); if (connection.HasAuthenticated) { LogManager.Log("Authenticated Identity: " + connection.AuthorizedIdentity.DisplayName); } return connection; } /// /// Creates a string representation of a VSTS field when reading. /// /// The field. /// private String GetFieldNameForRead(CoreField field) { return "System." + field.ToString(); } /// /// Creates a string representation of a VSTS field when writing. /// /// The field. /// private String GetFieldNameForWrite(CoreField field) { return "/fields/System." + field.ToString(); } /// /// Creates a string representation of a VSTS extension field when writing. /// /// The field. /// private String GetExtensionFieldNameForWrite(String extensionFieldName) { return "/fields/" + extensionFieldName; } /// /// Returns a value of the specified field key if found. Otherwise return an empty string. /// /// The fields. /// The key. /// private String TryGetField(IDictionary fields, String key) { if (fields.ContainsKey(key)) { return fields[key].ToString(); } return String.Empty; } /// /// Returns the specified enum type value by it's [Description] attribute. /// /// /// The description. /// private T ParseEnumByDescription(String description) { Dictionary values = new Dictionary(); foreach (var item in Enum.GetValues(typeof(T))) { values.Add(((Enum)item).ToDescription(), (T)item); } return values[description]; } private WorkItem ConvertToWorkItem(Project project, Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models.WorkItem item) { WorkItem workItem = new WorkItem(); workItem.ID = item.Id.Value; workItem.Title = item.Fields[GetFieldNameForRead(CoreField.Title)].ToString(); workItem.CreatedDate = DateTime.Parse(item.Fields[GetFieldNameForRead(CoreField.CreatedDate)].ToString()); workItem.Description = TryGetField(item.Fields, GetFieldNameForRead(CoreField.Description)); workItem.Area = new Area() { Path = item.Fields[GetFieldNameForRead(CoreField.AreaPath)].ToString(), Name = Path.GetFileName(item.Fields[GetFieldNameForRead(CoreField.AreaPath)].ToString()), }; workItem.Iteration = new Iteration() { Path = item.Fields[GetFieldNameForRead(CoreField.IterationPath)].ToString(), Name = Path.GetFileName(item.Fields[GetFieldNameForRead(CoreField.IterationPath)].ToString()), }; workItem.AssignedTo = project.Members.SingleOrDefault(x => x.AssignName == TryGetField(item.Fields, GetFieldNameForRead(CoreField.AssignedTo))); workItem.CreatedBy = project.Members.SingleOrDefault(x => x.AssignName == TryGetField(item.Fields, GetFieldNameForRead(CoreField.CreatedBy))); workItem.ChangedBy = project.Members.SingleOrDefault(x => x.AssignName == TryGetField(item.Fields, GetFieldNameForRead(CoreField.ChangedBy))); workItem.AuthorizedAs = project.Members.SingleOrDefault(x => x.AssignName == TryGetField(item.Fields, GetFieldNameForRead(CoreField.AuthorizedAs))); workItem.ResolvedBy = project.Members.SingleOrDefault(x => x.AssignName == TryGetField(item.Fields, ExtensionFields.RESOLVED_BY)); if (item.Fields.ContainsKey(ExtensionFields.RESOLVED_REASON)) { try { workItem.ResolvedReason = ParseEnumByDescription(item.Fields[ExtensionFields.RESOLVED_REASON].ToString()); if (item.Fields.ContainsKey(ExtensionFields.RESOLVED_DATE)) { workItem.ResolvedDate = DateTime.Parse(item.Fields[ExtensionFields.RESOLVED_DATE].ToString()); } } catch (Exception ex) { Debug.WriteLine($"Error parsing TFS work item resolved reason.\n{ex.ToString()}"); LogManager.Log(ex, "Error parsing TFS work item resolved reason."); } } workItem.Type = (WorkItemType)Enum.Parse(typeof(WorkItemType), item.Fields[GetFieldNameForRead(CoreField.WorkItemType)].ToString()); workItem.URL = item.Url; if (item.Fields.ContainsKey(GetFieldNameForRead(CoreField.Tags))) { List tags = item.Fields[GetFieldNameForRead(CoreField.Tags)].ToString().Split(';').Select(x => x.Trim()).ToList(); workItem.Tags = tags.Select(x => new Tag() { Name = x }).ToList(); } workItem.FoundInBuild = TryGetField(item.Fields, ExtensionFields.FOUND_IN_BUILD).ToString(); workItem.State = (State)Enum.Parse(typeof(State), item.Fields[GetFieldNameForRead(CoreField.State)].ToString()); if (item.Fields.ContainsKey(ExtensionFields.SEVERITY)) { workItem.Severity = ParseEnumByDescription(item.Fields[ExtensionFields.SEVERITY].ToString()); } workItem.Priority = (Priority)int.Parse(item.Fields[ExtensionFields.PRIORITY].ToString()); if (item.Fields.ContainsKey(ExtensionFields.STEPS_TO_REP)) { workItem.StepsToReproduce = TryGetField(item.Fields, ExtensionFields.STEPS_TO_REP).ToString(); } if (item.Fields.ContainsKey(ExtensionFields.SYSTEM_INFO)) { workItem.SystemInformation = TryGetField(item.Fields, ExtensionFields.SYSTEM_INFO).ToString(); } if (item.Fields.ContainsKey(ExtensionFields.LOGGED_IN_USER)) { workItem.LoggedInUser = TryGetField(item.Fields, ExtensionFields.LOGGED_IN_USER).ToString(); } if (item.Fields.ContainsKey(ExtensionFields.MACHINE_SN)) { workItem.MachineSerialNumber = TryGetField(item.Fields, ExtensionFields.MACHINE_SN).ToString(); } if (item.Fields.ContainsKey(ExtensionFields.ENVIRONMENT)) { workItem.Environment = TryGetField(item.Fields, ExtensionFields.ENVIRONMENT).ToString(); } if (item.Fields.ContainsKey(ExtensionFields.MACHINE_TYPE)) { workItem.MachineType = ParseEnumByDescription(item.Fields[ExtensionFields.MACHINE_TYPE].ToString()); } workItem.Comment = TryGetField(item.Fields, GetFieldNameForRead(CoreField.History)).ToString(); return workItem; } #endregion } }