public long?CalculateAssignedUserId(long boardId, string assignedTo) { if (!String.IsNullOrEmpty(assignedTo)) { if (_tfsUsers != null && _tfsUsers.Any()) { var user = _tfsUsers.FirstOrDefault(x => x != null && x.DisplayName != null && !String.IsNullOrEmpty(x.DisplayName) && x.DisplayName.ToLowerInvariant() == assignedTo.ToLowerInvariant()); if (user != null) { var lkUser = LeanKit.GetBoard(boardId).BoardUsers.FirstOrDefault(x => x != null && (((!String.IsNullOrEmpty(x.EmailAddress)) && (!String.IsNullOrEmpty(user.MailAddress)) && x.EmailAddress.ToLowerInvariant() == user.MailAddress.ToLowerInvariant()) || ((!String.IsNullOrEmpty(x.FullName)) && (!String.IsNullOrEmpty(user.DisplayName)) && x.FullName.ToLowerInvariant() == user.DisplayName.ToLowerInvariant()) || ((!String.IsNullOrEmpty(x.FullName)) && (!string.IsNullOrEmpty(user.DisplayName)) && x.FullName.ToLowerInvariant() == user.DisplayName.ToLowerInvariant()) || ((!String.IsNullOrEmpty(x.UserName)) && (!String.IsNullOrEmpty(user.AccountName)) && x.UserName.ToLowerInvariant() == user.AccountName.ToLowerInvariant()))); if (lkUser != null) { return(lkUser.Id); } } } } return(null); }
private void SetAssignedUser(WorkItem workItem, long boardId, long userId) { try { var lkUser = LeanKit.GetBoard(boardId).BoardUsers.FirstOrDefault(x => x.Id == userId); if (lkUser != null) { var gss = (IGroupSecurityService)_projectCollection.GetService(typeof(IGroupSecurityService)); var sids = gss.ReadIdentity(SearchFactor.AccountName, "Project Collection Valid Users", QueryMembership.Expanded); var users = gss.ReadIdentities(SearchFactor.Sid, sids.Members, QueryMembership.None); var tfsUser = users.FirstOrDefault(x => ((!string.IsNullOrEmpty(lkUser.EmailAddress)) && (!string.IsNullOrEmpty(x.MailAddress)) && lkUser.EmailAddress.ToLowerInvariant() == x.MailAddress.ToLowerInvariant()) || ((!string.IsNullOrEmpty(lkUser.FullName)) && (!string.IsNullOrEmpty(x.DisplayName)) && lkUser.FullName.ToLowerInvariant() == x.DisplayName.ToLowerInvariant()) || ((!string.IsNullOrEmpty(lkUser.UserName)) && (!string.IsNullOrEmpty(x.AccountName)) && lkUser.UserName.ToLowerInvariant() == x.AccountName.ToLowerInvariant())); if (tfsUser != null && tfsUser.DisplayName != null) { workItem.Fields["System.AssignedTo"].Value = tfsUser.DisplayName; } } } catch (Exception ex) { Log.Error(string.Format("An error occurred: {0} - {1} - {2}", ex.GetType(), ex.Message, ex.StackTrace)); } }
public override string ToString() { var sb = new StringBuilder(); sb.AppendLine(" LeanKit : " + LeanKit.ToString()); sb.AppendLine(" Target : " + Target); return(sb.ToString()); }
protected override void Synchronize(BoardMapping project) { Log.Debug("Polling GitHub for Pull Requests"); var queryAsOfDate = QueryDate.AddMilliseconds(Configuration.PollingFrequency * -1.5); //https://api.github.com/repos/{0}/{1}/pulls?state=Open var request = new RestRequest(string.Format("repos/{0}/{1}/pulls", Configuration.Target.Host, project.Identity.Target), Method.GET); request.AddParameter("state", project.QueryStates[0]); var resp = _restClient.Execute(request); if (resp.StatusCode != HttpStatusCode.OK) { var serializer = new JsonSerializer <ErrorMessage>(); var errorMessage = serializer.DeserializeFromString(resp.Content); Log.Error(string.Format("Unable to get Pull Requests from GitHub, Error: {0}. Check your board/repo mapping configuration.", errorMessage.Message)); return; } var pulls = new JsonSerializer <List <Pull> >().DeserializeFromString(resp.Content); Log.Info("\nQueried [{0}] at {1} for changes after {2}", project.Identity.Target, QueryDate, queryAsOfDate.ToString("o")); if (pulls != null && pulls.Any() && pulls[0].Id > 0) { foreach (var pull in pulls) { if (pull.Id > 0) { Log.Info("Pull Requests [{0}]: {1}, {2}, {3}", pull.Number, pull.Title, pull.User.Login, pull.State); // does this workitem have a corresponding card? var card = LeanKit.GetCardByExternalId(project.Identity.LeanKit, pull.Id + "|" + pull.Number.ToString()); if (card == null || !card.ExternalSystemName.Equals(ServiceName, StringComparison.OrdinalIgnoreCase)) { Log.Debug("Create new card for Pull Request [{0}]", pull.Number); CreateCardFromItem(project, pull); } else { Log.Debug("Previously created a card for Pull Request [{0}]", pull.Number); if (project.UpdateCards) { PullUpdated(pull, card, project); } else { Log.Info("Skipped card update because 'UpdateCards' is disabled."); } } } } Log.Info("{0} item(s) queried.\n", pulls.Count); } }
private void PullUpdated(Pull pull, Card card, BoardMapping boardMapping) { Log.Info("Pull [{0}] updated, comparing to corresponding card...", pull.Id); long boardId = boardMapping.Identity.LeanKit; // sync and save those items that are different (of title, description, priority) bool saveCard = false; if (pull.Title != card.Title) { card.Title = pull.Title; saveCard = true; } if (pull.Body.SanitizeCardDescription() != card.Description) { card.Description = pull.Body.SanitizeCardDescription(); saveCard = true; } var priority = pull.LeanKitPriority(); if (priority != card.Priority) { card.Priority = priority; saveCard = true; } if ((card.Tags == null || !card.Tags.Contains(ServiceName)) && boardMapping.TagCardsWithTargetSystemName) { if (string.IsNullOrEmpty(card.Tags)) { card.Tags = ServiceName; } else { card.Tags += "," + ServiceName; } saveCard = true; } if (saveCard) { Log.Info("Updating card [{0}]", card.Id); LeanKit.UpdateCard(boardId, card); } }
private void UpdateBoardVersion(long boardId, long?version = null) { if (!version.HasValue) { var board = LeanKit.GetBoard(boardId); if (board == null) { return; } version = board.Version; } if (AppSettings.BoardVersions.ContainsKey(boardId)) { AppSettings.BoardVersions[boardId] = version.Value; } else { AppSettings.BoardVersions.Add(boardId, version.Value); } LocalStorage.Save(AppSettings); }
public long?CalculateAssignedUserId(long boardId, Ticket ticket) { if (ticket == null) { return(null); } if (ticket.Assignee_Id > 0) { // http://mysubdomain.unfuddle.com/api/v1/people/{id} var request = new RestRequest(string.Format("/api/v1/people/{0}", ticket.Assignee_Id), Method.GET); var unfuddleResp = _restClient.Execute(request); if (unfuddleResp.StatusCode != HttpStatusCode.OK) { var serializer = new JsonSerializer <ErrorMessage>(); var errorMessage = serializer.DeserializeFromString(unfuddleResp.Content); Log.Warn(string.Format("Unable to get user from Unfuddle, Error: {0}", errorMessage.Message)); } else { var user = new JsonSerializer <Person>().DeserializeFromString(unfuddleResp.Content); if (user != null) { var lkUser = LeanKit.GetBoard(boardId).BoardUsers.FirstOrDefault(x => x != null && ((!string.IsNullOrEmpty(x.EmailAddress)) && (!string.IsNullOrEmpty(user.Email)) && x.EmailAddress.ToLowerInvariant() == user.Email.ToLowerInvariant()) || ((!string.IsNullOrEmpty(x.FullName)) && (!string.IsNullOrEmpty(user.Last_Name)) && x.FullName.ToLowerInvariant() == (user.First_Name + " " + user.Last_Name).ToLowerInvariant()) || ((!string.IsNullOrEmpty(x.UserName)) && (!string.IsNullOrEmpty(user.Username)) && x.UserName.ToLowerInvariant() == user.Username.ToLowerInvariant())); if (lkUser != null) { return(lkUser.Id); } } } } return(null); }
private void LoadBoardValues(BoardMapping boardMapping) { Board board = null; try { board = LeanKit.GetBoard(boardMapping.Identity.LeanKit); } catch (LeanKitAPIException ex) { Log.Error(ex, string.Format("Error getting Board: {0}", boardMapping.Identity.LeanKit)); } if (board == null) { return; } if (board.CardTypes != null && board.CardTypes.Any()) { boardMapping.ValidCardTypes = board.CardTypes; // check to make sure we have a default card type var defaultCard = boardMapping.ValidCardTypes.FirstOrDefault(x => x.IsDefault); if (defaultCard == null) { // if we do not have a default card type then check // to see if there is a Task card type and make that the default var taskCardType = boardMapping.ValidCardTypes.FirstOrDefault(x => x.Name.ToLowerInvariant() == "task"); if (taskCardType != null) { boardMapping.ValidCardTypes.FirstOrDefault(x => x.Name.ToLowerInvariant() == "task").IsDefault = true; } else { // otherwise just set the first card type to be the default boardMapping.ValidCardTypes.FirstOrDefault().IsDefault = true; } } } if (board.ArchiveTopLevelLaneId.HasValue) { boardMapping.ArchiveLaneId = board.ArchiveTopLevelLaneId.Value; } else { var archive = board.Archive.FirstOrDefault(x => x.ParentLaneId == 0); if (archive != null && archive.Id.HasValue) { boardMapping.ArchiveLaneId = archive.Id.Value; } if (boardMapping.ArchiveLaneId == 0) { var allLanes = board.AllLanes(); archive = allLanes.FirstOrDefault(x => x.ClassType == LaneClassType.Archive && x.ParentLaneId == 0); if (archive != null && archive.Id.HasValue) { boardMapping.ArchiveLaneId = archive.Id.Value; } } } if (board.Lanes != null && board.Lanes.Any()) { var validLanes = board.AllLanes().Where(x => x.ClassType != LaneClassType.Archive).OrderBy(x => x.Index).ToList(); var maxIndex = validLanes.Max(x => x.Index); // only use active lanes for the purpose of selecting the default drop lane var activeLanes = board.Lanes.Where(x => x.ClassType == LaneClassType.Active).OrderBy(x => x.Index).ToList(); var defaultDropLaneId = GetDefaultDropLane(activeLanes); boardMapping.ValidLanes = validLanes .Select(x => new Lane { Id = x.Id.Value, Name = x.Title, IsFirst = x.Id == defaultDropLaneId, ChildLaneIds = (x.ChildLaneIds != null && x.ChildLaneIds.Any()) ? x.ChildLaneIds : null, IsLast = (boardMapping.ArchiveLaneId > 0) ? x.Id == boardMapping.ArchiveLaneId : x.Index == maxIndex }) .ToList(); if (boardMapping.ArchiveLaneId > 0) { var archiveLane = board.GetLaneById(boardMapping.ArchiveLaneId); if (archiveLane != null) { boardMapping.ValidLanes.Add(new Lane { Id = boardMapping.ArchiveLaneId, Name = archiveLane.Title, IsFirst = false, IsLast = true }); } } } if (boardMapping.Types == null) { boardMapping.Types = new List <WorkItemType>(); } // values in LaneToStatesMap are assumed to valid, as they were configured using valid values. if (boardMapping.LaneToStatesMap == null) { Log.Fatal("An unexpected error occurred -- there is no valid lane-to-states mapping."); } }
private void CheckForMissedCardMoves(BoardMapping mapping) { // if we have local storage, we have saved board versions and we have one for this board long boardId = mapping.Identity.LeanKit; if (AppSettings != null && AppSettings.BoardVersions != null && AppSettings.BoardVersions.Any() && AppSettings.BoardVersions.ContainsKey(boardId)) { var version = AppSettings.BoardVersions[boardId]; Log.Debug(string.Format("Checking for any cards moved to mapped lanes on board [{0}] since service last ran, version [{1}].", boardId, version)); try { var events = LeanKit.GetBoardHistorySince(boardId, (int)version); var board = LeanKit.GetBoard(boardId); if (board != null && events != null) { foreach (var ev in events) { // check for created cards if (ev.EventType == "CardCreation") { var card = LeanKit.GetCard(board.Id, ev.CardId); if (card != null && string.IsNullOrEmpty(card.ExternalCardID)) { try { CreateNewItem(card.ToCard(), mapping); } catch (Exception e) { Log.Error("Exception for CreateNewItem: " + e.Message); } } } // only look for moved cards else if (ev.ToLaneId != 0) { var lane = board.GetLaneById(ev.ToLaneId); if (lane != null) { if (lane.Id.HasValue && mapping.LaneToStatesMap.Any() && mapping.LaneToStatesMap.ContainsKey(lane.Id.Value)) { if (mapping.LaneToStatesMap[lane.Id.Value] != null && mapping.LaneToStatesMap[lane.Id.Value].Count > 0) { // board.GetCard() only seems to get cards in active lanes // using LeanKitApi.GetCard() instead because it will get // cards in archive lanes var card = LeanKit.GetCard(board.Id, ev.CardId); if (card != null && !string.IsNullOrEmpty(card.ExternalCardID)) { try { UpdateStateOfExternalItem(card.ToCard(), mapping.LaneToStatesMap[lane.Id.Value], mapping); } catch (Exception e) { Log.Error("Exception for UpdateStateOfExternalItem: " + e.Message); } } } } } } } UpdateBoardVersion(board.Id, board.Version); } } catch (Exception ex) { Log.Error(string.Format("An error occured: {0} - {1} - {2}", ex.GetType(), ex.Message, ex.StackTrace)); } } }
protected override void CreateNewItem(Card card, BoardMapping boardMapping) { var jiraIssueType = GetJiraIssueType(boardMapping, card.TypeId); string json = "{ \"fields\": { "; json += "\"project\": { \"key\": \"" + boardMapping.Identity.Target + "\" }"; json += ", \"summary\": \"" + card.Title.Replace("\"", "\\\"") + "\" "; json += ", \"description\": \"" + (card.Description != null ? card.Description.Replace("</p>", "").Replace("<p>", "").Replace("\r", "\\r").Replace("\n", "\\n").Replace("\"", "\\\"") : "") + "\" "; json += ", \"issuetype\": { \"name\": \"" + jiraIssueType + "\" }"; json += ", \"priority\": { \"name\": \"" + GetPriority(card.Priority) + "\" }"; if (jiraIssueType.ToLowerInvariant() == "epic") { if (CustomFields.Any()) { var epicNameField = CustomFields.FirstOrDefault(x => x.Name == "Epic Name"); if (epicNameField != null) { json += ", \"" + epicNameField.Id + "\": \"" + card.Title.Replace("\"", "\\\"") + "\""; } } } if (!string.IsNullOrEmpty(card.DueDate) && CurrentUser != null) { try { var dateFormat = CurrentUser.DateFormat ?? "MM/dd/yyyy"; var parsed = DateTime.ParseExact(card.DueDate, dateFormat, CultureInfo.InvariantCulture); json += ", \"duedate\": \"" + parsed.ToString("o") + "\""; } catch (Exception ex) { Log.Warn(ex, "Could not parse due date: {0}", card.DueDate); } } if (!string.IsNullOrEmpty(card.Tags)) { var newLabels = card.Tags.Split(','); string updateLabels = ""; int ctr = 0; foreach (string newLabel in newLabels) { if (ctr > 0) { updateLabels += ", "; } updateLabels += "\"" + newLabel.Trim() + "\""; ctr++; } json += ", \"labels\": [" + updateLabels + "]"; } json += "}}"; Issue newIssue = null; try { //https://yoursite.atlassian.net/rest/api/latest/issue var createRequest = new RestRequest("/rest/api/latest/issue", Method.POST); createRequest.AddParameter("application/json", json, ParameterType.RequestBody); var resp = _restClient.Execute(createRequest); if (resp.StatusCode != HttpStatusCode.OK && resp.StatusCode != HttpStatusCode.Created) { var serializer = new JsonSerializer <ErrorMessage>(); var errorMessage = serializer.DeserializeFromString(resp.Content); Log.Error(string.Format("Unable to create Issue from card [{0}], Description: {1}, Message: {2}", card.ExternalCardID, resp.StatusDescription, errorMessage.Message)); } else { newIssue = new JsonSerializer <Issue>().DeserializeFromString(resp.Content); Log.Debug(String.Format("Created Issue [{0}]", newIssue.Key)); } } catch (Exception ex) { Log.Error(string.Format("Unable to create Issue from Card [{0}], Exception: {1}", card.ExternalCardID, ex.Message)); } if (newIssue != null) { try { card.ExternalCardID = newIssue.Key; card.ExternalSystemName = ServiceName; card.ExternalSystemUrl = string.Format(_externalUrlTemplate, newIssue.Key); // now that we've created the work item let's try to set it to any matching state defined by lane var states = boardMapping.LaneToStatesMap[card.LaneId]; if (states != null) { UpdateStateOfExternalItem(card, states, boardMapping, true); } LeanKit.UpdateCard(boardMapping.Identity.LeanKit, card); } catch (Exception ex) { Log.Error(string.Format("Error updating Card [{0}] after creating new Issue, Exception: {1}", card.ExternalCardID, ex.Message)); } } }
private void CreateCardFromItem(BoardMapping project, Issue issue) { if (issue == null) { return; } var boardId = project.Identity.LeanKit; var mappedCardType = issue.LeanKitCardType(project); var validLanes = project.LanesFromState(issue.Fields.Status.Name); var laneId = validLanes.Any() ? validLanes.First() : project.DefaultCardCreationLaneId; var card = new Card { Active = true, Title = issue.Fields.Summary, Description = issue.Fields.Description.SanitizeCardDescription(), Priority = issue.LeanKitPriority(), TypeId = mappedCardType.Id, TypeName = mappedCardType.Name, LaneId = laneId, ExternalCardID = issue.Key, ExternalSystemName = ServiceName, ExternalSystemUrl = string.Format(_externalUrlTemplate, issue.Key) }; var assignedUserId = issue.LeanKitAssignedUserId(boardId, LeanKit); if (assignedUserId != null) { card.AssignedUserIds = new[] { assignedUserId.Value } } ; if (issue.Fields != null && issue.Fields.DueDate != null && CurrentUser != null) { var dateFormat = CurrentUser.DateFormat ?? "MM/dd/yyyy"; card.DueDate = issue.Fields.DueDate.Value.ToString(dateFormat, CultureInfo.InvariantCulture); } if (issue.Fields != null && issue.Fields.Labels != null && issue.Fields.Labels.Any()) { card.Tags = string.Join(",", issue.Fields.Labels); } if ((card.Tags == null || !card.Tags.Contains(ServiceName)) && project.TagCardsWithTargetSystemName) { if (string.IsNullOrEmpty(card.Tags)) { card.Tags = ServiceName; } else { card.Tags += "," + ServiceName; } } // TODO: Add size from the custom story points field. Log.Info("Creating a card of type [{0}] for issue [{1}] on Board [{2}] on Lane [{3}]", mappedCardType.Name, issue.Key, boardId, laneId); CardAddResult cardAddResult = null; int tries = 0; bool success = false; while (tries < 10 && !success) { if (tries > 0) { Log.Error(string.Format("Attempting to create card for work item [{0}] attempt number [{1}]", issue.Key, tries)); // wait 5 seconds before trying again Thread.Sleep(new TimeSpan(0, 0, 5)); } try { cardAddResult = LeanKit.AddCard(boardId, card, "New Card From Jira Issue"); success = true; } catch (Exception ex) { Log.Error(string.Format("An error occurred: {0} - {1} - {2}", ex.GetType(), ex.Message, ex.StackTrace)); } tries++; } card.Id = cardAddResult.CardId; Log.Info("Created a card [{0}] of type [{1}] for work item [{2}] on Board [{3}] on Lane [{4}]", card.Id, mappedCardType.Name, issue.Key, boardId, laneId); }
protected override void Synchronize(BoardMapping project) { Log.Debug("Polling Jira for Issues"); var queryAsOfDate = QueryDate.AddMilliseconds(Configuration.PollingFrequency * -1.5); string jqlQuery; var formattedQueryDate = queryAsOfDate.ToString(QueryDateFormat, CultureInfo.InvariantCulture); if (!string.IsNullOrEmpty(project.Query)) { jqlQuery = string.Format(project.Query, formattedQueryDate); } else { var queryFilter = string.Format(" and ({0})", string.Join(" or ", project.QueryStates.Select(x => "status = '" + x.Trim() + "'").ToList())); if (!string.IsNullOrEmpty(project.ExcludedTypeQuery)) { queryFilter += project.ExcludedTypeQuery; } jqlQuery = string.Format("project=\"{0}\" {1} and updated > \"{2}\" order by created asc", project.Identity.Target, queryFilter, formattedQueryDate); } //https://yoursite.atlassian.net/rest/api/latest/search?jql=project=%22More+Tests%22+and+status=%22open%22+and+created+%3E+%222008/12/31+12:00%22+order+by+created+asc&fields=id,status,priority,summary,description var request = new RestRequest("/rest/api/latest/search", Method.GET); request.AddParameter("jql", jqlQuery); request.AddParameter("fields", "id,status,priority,summary,description,issuetype,type,assignee,duedate,labels"); request.AddParameter("maxResults", "9999"); var jiraResp = _restClient.Execute(request); if (jiraResp.StatusCode != HttpStatusCode.OK) { var serializer = new JsonSerializer <ErrorMessage>(); var errorMessage = serializer.DeserializeFromString(jiraResp.Content); Log.Error(string.Format("Unable to get issues from Jira, Error: {0}. Check your board/project mapping configuration.", errorMessage.Message)); return; } var resp = new JsonSerializer <IssuesResponse>().DeserializeFromString(jiraResp.Content); Log.Info("\nQueried [{0}] at {1} for changes after {2}", project.Identity.Target, QueryDate, queryAsOfDate.ToString("o")); if (resp != null && resp.Issues != null && resp.Issues.Any()) { var issues = resp.Issues; foreach (var issue in issues) { Log.Info("Issue [{0}]: {1}, {2}, {3}", issue.Key, issue.Fields.Summary, issue.Fields.Status.Name, issue.Fields.Priority.Name); // does this workitem have a corresponding card? var card = LeanKit.GetCardByExternalId(project.Identity.LeanKit, issue.Key); if (card == null || !card.ExternalSystemName.Equals(ServiceName, StringComparison.OrdinalIgnoreCase)) { Log.Debug("Create new card for Issue [{0}]", issue.Key); CreateCardFromItem(project, issue); } else { Log.Debug("Previously created a card for Issue [{0}]", issue.Key); if (project.UpdateCards) { IssueUpdated(issue, card, project); } else { Log.Info("Skipped card update because 'UpdateCards' is disabled."); } } } Log.Info("{0} item(s) queried.\n", issues.Count); } }
private void IssueUpdated(Issue issue, Card card, BoardMapping boardMapping) { Log.Info("Issue [{0}] updated, comparing to corresponding card...", issue.Key); long boardId = boardMapping.Identity.LeanKit; // sync and save those items that are different (of title, description, priority) bool saveCard = false; if (issue.Fields != null) { if (issue.Fields.Summary != null && issue.Fields.Summary != card.Title) { card.Title = issue.Fields.Summary; saveCard = true; } if (issue.Fields.Description != null && issue.Fields.Description.SanitizeCardDescription() != card.Description) { card.Description = issue.Fields.Description.SanitizeCardDescription(); saveCard = true; } var priority = issue.LeanKitPriority(); if (priority != card.Priority) { card.Priority = priority; saveCard = true; } if (issue.Fields.Labels != null && issue.Fields.Labels.Count > 0) { var tags = string.Join(",", issue.Fields.Labels.Select(x => x)); if (card.Tags != tags) { card.Tags = tags; saveCard = true; } } else if (!string.IsNullOrEmpty(card.Tags)) { card.Tags = ""; saveCard = true; } if ((card.Tags == null || !card.Tags.Contains(ServiceName)) && boardMapping.TagCardsWithTargetSystemName) { if (string.IsNullOrEmpty(card.Tags)) { card.Tags = ServiceName; } else { card.Tags += "," + ServiceName; } saveCard = true; } if (issue.Fields.DueDate != null && CurrentUser != null) { var dateFormat = CurrentUser.DateFormat ?? "MM/dd/yyyy"; var dueDateString = issue.Fields.DueDate.Value.ToString(dateFormat, CultureInfo.InvariantCulture); if (card.DueDate != dueDateString) { card.DueDate = dueDateString; saveCard = true; } } else if (!string.IsNullOrEmpty(card.DueDate)) { card.DueDate = ""; saveCard = true; } } if (saveCard) { Log.Info("Updating card [{0}]", card.Id); LeanKit.UpdateCard(boardId, card); } // check the state of the work item // if we have the state mapped to a lane then check to see if the card is in that lane // if it is not in that lane then move it to that lane if (boardMapping.UpdateCardLanes && issue.Fields != null && issue.Fields.Status != null && !string.IsNullOrEmpty(issue.Fields.Status.Name)) { // if card is already in archive lane then we do not want to move it to the end lane // because it is effectively the same thing with respect to integrating with TFS if (card.LaneId == boardMapping.ArchiveLaneId) { return; } var laneIds = boardMapping.LanesFromState(issue.Fields.Status.Name); if (laneIds.Any()) { if (!laneIds.Contains(card.LaneId)) { // first let's see if any of the lanes are sibling lanes, if so then // we should be using one of them. So we'll limit the results to just siblings if (boardMapping.ValidLanes != null) { var siblingLaneIds = (from siblingLaneId in laneIds let parentLane = boardMapping.ValidLanes.FirstOrDefault(x => x.HasChildLanes && x.ChildLaneIds.Contains(siblingLaneId) && x.ChildLaneIds.Contains(card.LaneId)) where parentLane != null select siblingLaneId).ToList(); if (siblingLaneIds.Any()) { laneIds = siblingLaneIds; } } LeanKit.MoveCard(boardMapping.Identity.LeanKit, card.Id, laneIds.First(), 0, "Moved Lane From Jira Issue"); } } } }
private void MapChildLanes(IList<LeanKit.API.Client.Library.TransferObjects.Lane> lanes, LeanKit.API.Client.Library.TransferObjects.Lane parentLane, LaneModel parentLaneModel, int level) { parentLaneModel.ChildLanes = new List<LaneModel>(); parentLaneModel.IsParent = false; parentLaneModel.Level = level; if (parentLane.ChildLaneIds.Count == 0) return; parentLaneModel.IsParent = true; level++; foreach (var childLaneId in parentLane.ChildLaneIds) { var childLane = lanes.FirstOrDefault(x => x.Id == childLaneId); var childLaneModel = Mapper.Map<LaneModel>(childLane); MapChildLanes(lanes, childLane, childLaneModel, level); childLaneModel.Level = level; parentLaneModel.ChildLanes.Add(childLaneModel); } }
private void WorkItemUpdated(WorkItem workItem, Card card, BoardMapping project) { Log.Info("WorkItem [{0}] updated, comparing to corresponding card...", workItem.Id); var boardId = project.Identity.LeanKit; // sync and save those items that are different (of title, description, priority) var saveCard = false; if (workItem.Title != card.Title) { card.Title = workItem.Title; saveCard = true; } var description = workItem.LeanKitDescription(GetTfsVersion()); if (description != card.Description) { card.Description = description; saveCard = true; } var priority = workItem.LeanKitPriority(); if (priority != card.Priority) { card.Priority = priority; saveCard = true; } if (workItem.Fields != null && workItem.Fields.Contains("Tags") && workItem.Fields["Tags"] != null && workItem.Fields["Tags"].Value.ToString() != card.Tags) { var tfsTags = workItem.Fields["Tags"].Value.ToString(); // since we cannot set the tags in TFS we cannot blindly overwrite the LK tags // with what is in TFS. Instead we can only add TFS tags to LK if (!string.IsNullOrEmpty(tfsTags)) { var tfsTagsArr = tfsTags.Split(','); foreach (var tag in tfsTagsArr) { if (card.Tags.ToLowerInvariant().Contains(tag.ToLowerInvariant())) { continue; } if (card.Tags == string.Empty) { card.Tags = tag; } else { card.Tags += "," + tag; } saveCard = true; } } } if (workItem.Fields != null && (workItem.Fields.Contains("Original Estimate") || workItem.Fields.Contains("Story Points"))) { if (workItem.Fields.Contains("Original Estimate") && workItem.Fields["Original Estimate"] != null && workItem.Fields["Original Estimate"].Value != null) { double cardSize; var isNumber = Double.TryParse(workItem.Fields["Original Estimate"].Value.ToString(), out cardSize); if (isNumber) { var size = (int)cardSize; if (card.Size != size) { card.Size = size; saveCard = true; } } } else if (workItem.Fields.Contains("Story Points") && workItem.Fields["Story Points"] != null && workItem.Fields["Story Points"].Value != null) { double cardSize; var isNumber = Double.TryParse(workItem.Fields["Story Points"].Value.ToString(), out cardSize); if (isNumber) { var size = (int)cardSize; if (card.Size != size) { card.Size = size; saveCard = true; } } } } if ((card.Tags == null || !card.Tags.Contains(ServiceName)) && project.TagCardsWithTargetSystemName) { if (string.IsNullOrEmpty(card.Tags)) { card.Tags = ServiceName; } else { card.Tags += "," + ServiceName; } saveCard = true; } if (saveCard) { Log.Info("Updating card [{0}]", card.Id); LeanKit.UpdateCard(boardId, card); } // check the state of the work item // if we have the state mapped to a lane then check to see if the card is in that lane // if it is not in that lane then move it to that lane if (!project.UpdateCardLanes || string.IsNullOrEmpty(workItem.State)) { return; } // if card is already in archive lane then we do not want to move it to the end lane // because it is effectively the same thing with respect to integrating with TFS if (card.LaneId == project.ArchiveLaneId) { return; } var laneIds = project.LanesFromState(workItem.State); if (laneIds.Any()) { if (!laneIds.Contains(card.LaneId)) { // first let's see if any of the lanes are sibling lanes, if so then // we should be using one of them. So we'll limit the results to just siblings if (project.ValidLanes != null) { var siblingLaneIds = (from siblingLaneId in laneIds let parentLane = project.ValidLanes.FirstOrDefault(x => x.HasChildLanes && x.ChildLaneIds.Contains(siblingLaneId) && x.ChildLaneIds.Contains(card.LaneId)) where parentLane != null select siblingLaneId).ToList(); if (siblingLaneIds.Any()) { laneIds = siblingLaneIds; } } LeanKit.MoveCard(project.Identity.LeanKit, card.Id, laneIds.First(), 0, "Moved Lane From TFS Work Item"); } } }
private void IssueUpdated(Issue issue, Card card, BoardMapping boardMapping) { Log.Info("Issue [{0}] updated, comparing to corresponding card...", issue.Id); long boardId = boardMapping.Identity.LeanKit; // sync and save those items that are different (of title, description, priority) bool saveCard = false; if (issue.Title != card.Title) { card.Title = issue.Title; saveCard = true; } if (issue.Body.SanitizeCardDescription() != card.Description) { card.Description = issue.Body.SanitizeCardDescription(); saveCard = true; } var priority = issue.LeanKitPriority(); if (priority != card.Priority) { card.Priority = priority; saveCard = true; } if (issue.Labels != null && issue.Labels.Count > 0) { var tags = string.Join(",", issue.Labels.Select(x => x.Name)); if (card.Tags != tags) { card.Tags = tags; saveCard = true; } } else if (!string.IsNullOrEmpty(card.Tags)) { card.Tags = ""; saveCard = true; } if (issue.Milestone != null && issue.Milestone.Due_On != null) { if (CurrentUser != null) { var dateFormat = CurrentUser.DateFormat ?? "MM/dd/yyyy"; var dueDateString = issue.Milestone.Due_On.Value.ToString(dateFormat); if (card.DueDate != dueDateString) { card.DueDate = dueDateString; saveCard = true; } } } else if (!string.IsNullOrEmpty(card.DueDate)) { card.DueDate = ""; saveCard = true; } if ((card.Tags == null || !card.Tags.Contains(ServiceName)) && boardMapping.TagCardsWithTargetSystemName) { if (string.IsNullOrEmpty(card.Tags)) { card.Tags = ServiceName; } else { card.Tags += "," + ServiceName; } saveCard = true; } var lanes = boardMapping.LanesFromState(issue.State); if (lanes.Count > 0 && lanes.All(x => x != card.LaneId)) { card.LaneId = lanes.First(); saveCard = true; } if (saveCard) { Log.Info("Updating card [{0}]", card.Id); LeanKit.UpdateCard(boardId, card); } }
protected override void Synchronize(BoardMapping project) { Log.Debug("Polling TFS [{0}] for Work Items", project.Identity.TargetName); //query a project for new items var stateQuery = string.Format(" AND ({0})", String.Join(" or ", project.QueryStates.Select(x => "[System.State] = '" + x.Trim() + "'").ToList())); var iterationQuery = ""; if (!string.IsNullOrEmpty(project.IterationPath)) { iterationQuery = string.Format(" AND [System.IterationPath] UNDER '{0}' ", project.IterationPath); } var queryAsOfDate = QueryDate.AddMilliseconds(Configuration.PollingFrequency * -1.5).ToString("o"); string tfsQuery; if (!string.IsNullOrEmpty(project.Query)) { tfsQuery = string.Format(project.Query, queryAsOfDate); } else { tfsQuery = String.Format( "[System.TeamProject] = '{0}' {1} {2} {3} and [System.ChangedDate] > '{4}'", project.Identity.TargetName, iterationQuery, stateQuery, project.ExcludedTypeQuery, queryAsOfDate); } var queryStr = string.Format("SELECT [System.Id], [System.WorkItemType]," + " [System.State], [System.AssignedTo], [System.Title], [System.Description]" + " FROM WorkItems " + " WHERE {0}" + " ORDER BY [System.TeamProject]", tfsQuery); if (_projectCollectionWorkItemStore == null) { "Reconnecting to TFS...".Info(); Init(); } Query query; try { query = new Query(_projectCollectionWorkItemStore, queryStr, null, false); } catch (Exception ex) { Log.Error("Error creating TFS query. {0} ", ex.Message); if (_projectCollectionWorkItemStore == null) { Log.Error("Project Collection Work Item Store is null"); } throw; } var cancelableAsyncResult = query.BeginQuery(); var changedItems = query.EndQuery(cancelableAsyncResult); Log.Info("\nQuery [{0}] for changes after {1}", project.Identity.Target, queryAsOfDate); Log.Debug(queryStr); foreach (WorkItem item in changedItems) { Log.Info("Work Item [{0}]: {1}, {2}, {3}", item.Id, item.Title, item.Fields["System.AssignedTo"].Value, item.State); // does this workitem have a corresponding card? var card = LeanKit.GetCardByExternalId(project.Identity.LeanKit, item.Id.ToString(CultureInfo.InvariantCulture)); if (card == null || !card.ExternalSystemName.Equals(ServiceName, StringComparison.OrdinalIgnoreCase)) { Log.Debug("Creating new card for work item [{0}]", item.Id); CreateCardFromWorkItem(project, item); } // TODO: else if Lane = defined end lane then update it in TFS (i.e. we missed the event) // call UpdateStateOfExternalWorkItem() else { Log.Info("Previously created a card for work item[{0}]", item.Id); if (project.UpdateCards) { WorkItemUpdated(item, card, project); } else { Log.Info("Skipped card update because 'UpdateCards' is disabled."); } } } Log.Info("{0} item(s) queried.\n", changedItems.Count); }
private void CreateCardFromItem(BoardMapping project, Ticket ticket) { if (ticket == null) { return; } var boardId = project.Identity.LeanKit; var mappedCardType = ticket.LeanKitCardType(project); var laneId = project.LanesFromState(ticket.Status).First(); var card = new Card { Active = true, Title = ticket.Summary, Description = ticket.Description.SanitizeCardDescription(), Priority = ticket.LeanKitPriority(), TypeId = mappedCardType.Id, TypeName = mappedCardType.Name, LaneId = laneId, ExternalCardID = ticket.Id.ToString(), ExternalSystemName = ServiceName, ExternalSystemUrl = string.Format(_externalUrlTemplate, ticket.Id, project.Identity.Target) }; var assignedUserId = CalculateAssignedUserId(boardId, ticket); if (assignedUserId != null) { card.AssignedUserIds = new[] { assignedUserId.Value } } ; if (ticket.Due_On != null) { if (CurrentUser != null) { var dateFormat = CurrentUser.DateFormat ?? "MM/dd/yyyy"; card.DueDate = ticket.Due_On.Value.ToString(dateFormat); } } if ((card.Tags == null || !card.Tags.Contains(ServiceName)) && project.TagCardsWithTargetSystemName) { if (string.IsNullOrEmpty(card.Tags)) { card.Tags = ServiceName; } else { card.Tags += "," + ServiceName; } } Log.Info("Creating a card of type [{0}] for ticket [{1}] on Board [{2}] on Lane [{3}]", mappedCardType.Name, ticket.Id, boardId, laneId); CardAddResult cardAddResult = null; int tries = 0; bool success = false; while (tries < 10 && !success) { if (tries > 0) { Log.Error(string.Format("Attempting to create card for ticket [{0}] attempt number [{1}]", ticket.Id, tries)); // wait 5 seconds before trying again Thread.Sleep(new TimeSpan(0, 0, 5)); } try { cardAddResult = LeanKit.AddCard(boardId, card, "New Card From Unfuddle Ticket"); success = true; } catch (Exception ex) { Log.Error(string.Format("An error occurred: {0} - {1} - {2}", ex.GetType(), ex.Message, ex.StackTrace)); } tries++; } if (cardAddResult != null) { card.Id = cardAddResult.CardId; } Log.Info("Created a card [{0}] of type [{1}] for ticket [{2}] on Board [{3}] on Lane [{4}]", card.Id, mappedCardType.Name, ticket.Id, boardId, laneId); }
protected override void Synchronize(BoardMapping project) { Log.Debug("Polling Unfuddle for Tickets"); var queryAsOfDate = QueryDate.AddMilliseconds(Configuration.PollingFrequency * -1.5); var unfuddleQuery = !string.IsNullOrEmpty(project.Query) ? string.Format(project.Query, queryAsOfDate.ToString("yyyy/MM/dd hh:mm")) : string.Format("status-eq-{0},created_at-gt-{1}", project.QueryStates[0], queryAsOfDate.ToString("yyyy/MM/dd hh:mm")); //http://mysubdomain.unfuddle.com/api/v1/projects/{id}/ticket_reports/dynamic?sort_by=created_at&sort_direction=ASC&conditions_string=status-eq-new,created_at-gt-yyyy/MM/dd hh:mm var request = new RestRequest(string.Format("/api/v1/projects/{0}/ticket_reports/dynamic", project.Identity.Target), Method.GET); request.AddParameter("sort_by", "created_at"); request.AddParameter("sort_direction", "ASC"); request.AddParameter("conditions_string", unfuddleQuery); //, "id,status,priority,summary,description,type"); request.AddParameter("limit", "500"); var unfuddleResp = _restClient.Execute(request); if (unfuddleResp.StatusCode != HttpStatusCode.OK) { var serializer = new JsonSerializer <ErrorMessage>(); var errorMessage = serializer.DeserializeFromString(unfuddleResp.Content); Log.Error(string.Format("Unable to get tickets from Unfuddle, Error: {0}. Check your board/project mapping configuration.", errorMessage.Message)); return; } var resp = new JsonSerializer <TicketsResponse>().DeserializeFromString(unfuddleResp.Content); Log.Info("\nQueried [{0}] at {1} for changes after {2}", project.Identity.Target, QueryDate, queryAsOfDate.ToString("o")); if (resp != null && resp.Groups != null && resp.Groups.Any()) { foreach (var group in resp.Groups) { if (group != null && group.Tickets != null && group.Tickets.Any()) { var tickets = group.Tickets; foreach (var ticket in tickets) { Log.Info("Ticket [{0}]: {1}, {2}, {3}", ticket.Id, ticket.Summary, ticket.Status, ticket.Priority); // does this workitem have a corresponding card? var card = LeanKit.GetCardByExternalId(project.Identity.LeanKit, ticket.Id.ToString()); if (card == null || !card.ExternalSystemName.Equals(ServiceName, StringComparison.OrdinalIgnoreCase)) { Log.Debug("Create new card for Ticket [{0}]", ticket.Id); CreateCardFromItem(project, ticket); } else { Log.Debug("Previously created a card for Ticket [{0}]", ticket.Id); if (project.UpdateCards) { TicketUpdated(ticket, card, project); } else { Log.Info("Skipped card update because 'UpdateCards' is disabled."); } } } } } Log.Info("{0} item(s) queried.\n", resp.Count); } }
private void TicketUpdated(Ticket ticket, Card card, BoardMapping boardMapping) { Log.Info("Ticket [{0}] updated, comparing to corresponding card...", ticket.Id); long boardId = boardMapping.Identity.LeanKit; // sync and save those items that are different (of title, description, priority) bool saveCard = false; if (ticket.Summary != card.Title) { card.Title = ticket.Summary; saveCard = true; } if (ticket.Description.SanitizeCardDescription() != card.Description) { card.Description = ticket.Description.SanitizeCardDescription(); saveCard = true; } var priority = ticket.LeanKitPriority(); if (priority != card.Priority) { card.Priority = priority; saveCard = true; } if (ticket.Due_On != null) { if (CurrentUser != null) { var dateFormat = CurrentUser.DateFormat ?? "MM/dd/yyyy"; var dueDateString = ticket.Due_On.Value.ToString(dateFormat); if (card.DueDate != dueDateString) { card.DueDate = dueDateString; saveCard = true; } } } else if (!string.IsNullOrEmpty(card.DueDate)) { card.DueDate = ""; saveCard = true; } if ((card.Tags == null || !card.Tags.Contains(ServiceName)) && boardMapping.TagCardsWithTargetSystemName) { if (string.IsNullOrEmpty(card.Tags)) { card.Tags = ServiceName; } else { card.Tags += "," + ServiceName; } saveCard = true; } if (saveCard) { Log.Info("Updating card [{0}]", card.Id); LeanKit.UpdateCard(boardId, card); } }
protected override void CreateNewItem(Card card, BoardMapping boardMapping) { var project = _projectCollectionWorkItemStore.Projects[boardMapping.Identity.TargetName]; var tfsWorkItemType = GetTfsWorkItemType(boardMapping, project.WorkItemTypes, card.TypeId); var workItemType = project.WorkItemTypes[tfsWorkItemType.Name]; // Note: using the default state var workItem = new WorkItem(workItemType) { Title = card.Title, Description = card.Description, }; SetWorkItemPriority(workItem, card.Priority); if (!string.IsNullOrEmpty(card.DueDate)) { if (workItem.Fields.Contains("Due Date")) { workItem.Fields["Due Date"].Value = card.DueDate; } } if (card.AssignedUserIds != null && card.AssignedUserIds.Any()) { SetAssignedUser(workItem, boardMapping.Identity.LeanKit, card.AssignedUserIds[0]); } if (!string.IsNullOrEmpty(boardMapping.IterationPath)) { workItem.Fields["System.IterationPath"].Value = boardMapping.IterationPath; } try { Log.Debug("Attempting to create Work Item from Card [{0}]", card.Id); workItem.Save(); Log.Debug("Created Work Item [{0}] from Card [{1}]", workItem.Id, card.Id); card.ExternalCardID = workItem.Id.ToString(CultureInfo.InvariantCulture); card.ExternalSystemName = ServiceName; if (_projectHyperlinkService != null) { card.ExternalSystemUrl = _projectHyperlinkService.GetWorkItemEditorUrl(workItem.Id).ToString(); } // now that we've created the work item let's try to set it to any matching state defined by lane var states = boardMapping.LaneToStatesMap[card.LaneId]; if (states != null) { UpdateStateOfExternalItem(card, states, boardMapping, true); } LeanKit.UpdateCard(boardMapping.Identity.LeanKit, card); } catch (ValidationException ex) { Log.Error("Unable to create WorkItem from Card [{0}]. ValidationException: {1}", card.Id, ex.Message); } catch (Exception ex) { Log.Error("Unable to create WorkItem from Card [{0}], Exception: {1}", card.Id, ex.Message); } }
// This method is based on method of same name from Kanban.ApplicationServices.BoardService private LeanKit.API.Client.Library.TransferObjects.Lane FindFirstChildLane(LeanKit.API.Client.Library.TransferObjects.Lane parentLane, IList<LeanKit.API.Client.Library.TransferObjects.Lane> allLanes) { if (parentLane == null) return null; return (parentLane.ChildLaneIds != null && parentLane.ChildLaneIds.Any()) ? FindFirstChildLane(allLanes.Where(x => x.ParentLaneId == parentLane.Id).OrderBy(x => x.Index).FirstOrDefault(), allLanes) : parentLane; }
private void CreateCardFromItem(BoardMapping project, Pull pull) { if (pull == null) { return; } var boardId = project.Identity.LeanKit; var mappedCardType = pull.LeanKitCardType(project); var laneId = project.LanesFromState(pull.State).First(); var card = new Card { Active = true, Title = pull.Title, Description = pull.Body.SanitizeCardDescription(), Priority = pull.LeanKitPriority(), TypeId = mappedCardType.Id, TypeName = mappedCardType.Name, LaneId = laneId, ExternalCardID = pull.Id.ToString() + "|" + pull.Number.ToString(), ExternalSystemName = ServiceName, ExternalSystemUrl = string.Format(_externalUrlTemplate, project.Identity.Target, pull.Number) }; var assignedUserId = pull.LeanKitAssignedUser(boardId, LeanKit); if (assignedUserId != null) { card.AssignedUserIds = new[] { assignedUserId.Value } } ; if ((card.Tags == null || !card.Tags.Contains(ServiceName)) && project.TagCardsWithTargetSystemName) { if (string.IsNullOrEmpty(card.Tags)) { card.Tags = ServiceName; } else { card.Tags += "," + ServiceName; } } Log.Info("Creating a card of type [{0}] for Pull Request [{1}] on Board [{2}] on Lane [{3}]", mappedCardType.Name, pull.Number, boardId, laneId); CardAddResult cardAddResult = null; int tries = 0; bool success = false; while (tries < 10 && !success) { if (tries > 0) { Log.Error(string.Format("Attempting to create card for Pull Request [{0}] attempt number [{1}]", pull.Id, tries)); // wait 5 seconds before trying again Thread.Sleep(new TimeSpan(0, 0, 5)); } try { cardAddResult = LeanKit.AddCard(boardId, card, "New Card From GitHub Pull Request"); success = true; } catch (Exception ex) { Log.Error(string.Format("An error occurred: {0} - {1} - {2}", ex.GetType(), ex.Message, ex.StackTrace)); } tries++; } if (cardAddResult != null) { card.Id = cardAddResult.CardId; } Log.Info("Created a card [{0}] of type [{1}] for Pull Request [{2}] on Board [{3}] on Lane [{4}]", card.Id, mappedCardType.Name, pull.Number, boardId, laneId); }
private void CreateCardFromItem(BoardMapping project, Issue issue) { if (issue == null) { return; } if (!project.CreateCards) { Log.Debug("CreateCards is disabled, skipping card creation."); return; } var boardId = project.Identity.LeanKit; var mappedCardType = issue.LeanKitCardType(project); var laneId = project.LanesFromState(issue.State).First(); var card = new Card { Active = true, Title = issue.Title, Description = issue.Body.SanitizeCardDescription(), Priority = issue.LeanKitPriority(), TypeId = mappedCardType.Id, TypeName = mappedCardType.Name, LaneId = laneId, ExternalCardID = issue.Id + "|" + issue.Number, ExternalSystemName = ServiceName, ExternalSystemUrl = string.Format(_externalUrlTemplate, project.Identity.Target, issue.Number), Index = 9999 }; var assignedUserId = issue.LeanKitAssignedUserId(boardId, LeanKit); if (assignedUserId != null) { card.AssignedUserIds = new[] { assignedUserId.Value } } ; if (issue.Milestone != null && issue.Milestone.Due_On != null) { if (CurrentUser != null) { var dateFormat = CurrentUser.DateFormat ?? "MM/dd/yyyy"; card.DueDate = issue.Milestone.Due_On.Value.ToString(dateFormat); } } if (issue.Labels != null && issue.Labels.Any()) { card.Tags = string.Join(",", issue.Labels.Select(x => x.Name).ToList()); } if ((card.Tags == null || !card.Tags.Contains(ServiceName)) && project.TagCardsWithTargetSystemName) { if (string.IsNullOrEmpty(card.Tags)) { card.Tags = ServiceName; } else { card.Tags += "," + ServiceName; } } Log.Info("Creating a card of type [{0}] for Issue [{1}] on Board [{2}] on Lane [{3}]", mappedCardType.Name, issue.Number, boardId, laneId); CardAddResult cardAddResult = null; int tries = 0; bool success = false; while (tries < 10 && !success) { if (tries > 0) { Log.Error(string.Format("Attempting to create card for issue [{0}] attempt number [{1}]", issue.Id, tries)); // wait 5 seconds before trying again Thread.Sleep(new TimeSpan(0, 0, 5)); } try { cardAddResult = LeanKit.AddCard(boardId, card, "New Card From GitHub Issue"); success = true; } catch (Exception ex) { Log.Error(string.Format("An error occurred: {0} - {1} - {2}", ex.GetType(), ex.Message, ex.StackTrace)); } tries++; } card.Id = cardAddResult.CardId; Log.Info("Created a card [{0}] of type [{1}] for Issue [{2}] on Board [{3}] on Lane [{4}]", card.Id, mappedCardType.Name, issue.Number, boardId, laneId); }
private void CreateCardFromWorkItem(BoardMapping project, WorkItem workItem) { if (workItem == null) { return; } var boardId = project.Identity.LeanKit; var mappedCardType = workItem.LeanKitCardType(project); var laneId = project.LanesFromState(workItem.State).First(); var card = new Card { Active = true, Title = workItem.Title, Description = workItem.LeanKitDescription(GetTfsVersion()), Priority = workItem.LeanKitPriority(), TypeId = mappedCardType.Id, TypeName = mappedCardType.Name, LaneId = laneId, ExternalCardID = workItem.Id.ToString(CultureInfo.InvariantCulture), ExternalSystemName = ServiceName }; if (workItem.Fields.Contains("Tags") && workItem.Fields["Tags"] != null && workItem.Fields["Tags"].Value != null) { card.Tags = workItem.Fields["Tags"].Value.ToString(); } if (project.TagCardsWithTargetSystemName && (card.Tags == null || !card.Tags.Contains(ServiceName))) { if (string.IsNullOrEmpty(card.Tags)) { card.Tags = ServiceName; } else { card.Tags += "," + ServiceName; } } if (_projectHyperlinkService != null) { card.ExternalSystemUrl = _projectHyperlinkService.GetWorkItemEditorUrl(workItem.Id).ToString(); } if (workItem.Fields != null && workItem.Fields.Contains("Assigned To")) { if (workItem.Fields["Assigned To"] != null && workItem.Fields["Assigned To"].Value != null) { var assignedUserId = CalculateAssignedUserId(boardId, workItem.Fields["Assigned To"].Value.ToString()); if (assignedUserId != null) { card.AssignedUserIds = new[] { assignedUserId.Value } } ; } } if (workItem.Fields != null && workItem.Fields.Contains("Due Date")) { if (workItem.Fields["Due Date"] != null && workItem.Fields["Due Date"].Value != null) { DateTime tfsDueDate; var isDate = DateTime.TryParse(workItem.Fields["Due Date"].Value.ToString(), out tfsDueDate); if (isDate) { if (CurrentUser != null) { var dateFormat = CurrentUser.DateFormat ?? "MM/dd/yyyy"; card.DueDate = tfsDueDate.ToString(dateFormat); } } } } if (workItem.Fields != null && (workItem.Fields.Contains("Original Estimate") || workItem.Fields.Contains("Story Points"))) { if (workItem.Fields.Contains("Original Estimate") && workItem.Fields["Original Estimate"] != null && workItem.Fields["Original Estimate"].Value != null) { double cardSize; var isNumber = Double.TryParse(workItem.Fields["Original Estimate"].Value.ToString(), out cardSize); if (isNumber) { card.Size = (int)cardSize; } } else if (workItem.Fields.Contains("Story Points") && workItem.Fields["Story Points"] != null && workItem.Fields["Story Points"].Value != null) { double cardSize; var isNumber = Double.TryParse(workItem.Fields["Story Points"].Value.ToString(), out cardSize); if (isNumber) { card.Size = (int)cardSize; } } } Log.Info("Creating a card of type [{0}] for work item [{1}] on Board [{2}] on Lane [{3}]", mappedCardType.Name, workItem.Id, boardId, laneId); CardAddResult cardAddResult = null; var tries = 0; var success = false; while (tries < 10 && !success) { if (tries > 0) { Log.Warn(String.Format("Attempting to create card for work item [{0}] attempt number [{1}]", workItem.Id, tries)); // wait 5 seconds before trying again Thread.Sleep(new TimeSpan(0, 0, 5)); } try { cardAddResult = LeanKit.AddCard(boardId, card, "New Card From TFS Work Item"); success = true; } catch (Exception ex) { Log.Error(ex, string.Format("An error occurred creating a new card for work item [{0}]", workItem.Id)); } tries++; } card.Id = cardAddResult.CardId; Log.Info("Created a card [{0}] of type [{1}] for work item [{2}] on Board [{3}] on Lane [{4}]", card.Id, mappedCardType.Name, workItem.Id, boardId, laneId); }