public static CardType CalculateLeanKitCardType(BoardMapping project, string issueTypeName) { if (string.IsNullOrEmpty(issueTypeName)) { return(project.ValidCardTypes.FirstOrDefault(x => x.IsDefault)); } var mappedWorkType = project.Types.FirstOrDefault(x => x.Target.Equals(issueTypeName, StringComparison.InvariantCultureIgnoreCase)); if (mappedWorkType != null) { var definedVal = project.ValidCardTypes.FirstOrDefault( x => x.Name.Equals(mappedWorkType.LeanKit, StringComparison.InvariantCultureIgnoreCase)); if (definedVal != null) { return(definedVal); } } var implicitVal = project.ValidCardTypes.FirstOrDefault(x => x.Name.Equals(issueTypeName, StringComparison.InvariantCultureIgnoreCase)); return(implicitVal ?? project.ValidCardTypes.FirstOrDefault(x => x.IsDefault)); }
public static CardType CalculateLeanKitCardType(BoardMapping project, GitHubIssues.Issue issue) { // NOTE: GitHub does not use types for issues. It uses labels. // Default labels are: bug, duplicate, enhancement, invalid, question, wont fix // of those bug and enhancement are the ones that fit a type the best. // bug could be mapped to bug/issue in LeanKit and enhancement mapped to improvement/feature in LeanKit var defaultCardType = project.ValidCardTypes.FirstOrDefault(x => x.IsDefault); if (issue != null && issue.Labels != null && issue.Labels.Any()) { foreach (var label in issue.Labels) { var mappedWorkType = project.Types.FirstOrDefault(x => x.Target.ToLowerInvariant() == label.Name.ToLowerInvariant()); if (mappedWorkType != null) { var definedVal = project.ValidCardTypes.FirstOrDefault(x => x.Name.ToLowerInvariant() == mappedWorkType.LeanKit.ToLowerInvariant()); if (definedVal != null) { return(definedVal); } } var implicitVal = project.ValidCardTypes.FirstOrDefault(x => x.Name.ToLowerInvariant() == label.Name.ToLowerInvariant()); if (implicitVal != null) { return(implicitVal); } } } return(defaultCardType); }
protected override void OnStartFixture() { _testBoard = Test <Board> .Item; foreach (var cardType in _testBoard.CardTypes) { cardType.IsDefault = false; } _testBoard.CardTypes.Add(new CardType() { Id = 999, Name = "Willy", IsDefault = false }); _testBoard.CardTypes.Last().IsDefault = true; _mapping = Test <BoardMapping> .Item; _mapping.Identity.LeanKit = _testBoard.Id; _mapping.Types = new List <WorkItemType>() { new WorkItemType() { LeanKit = "Willy", Target = "Roger" } }; TestConfig = Test <Configuration> .Item; TestConfig.Mappings = new List <BoardMapping> { _mapping }; }
public static CardType CalculateLeanKitCardType(BoardMapping project, GitHubIssues.Issue issue) { // NOTE: GitHub does not use types for issues. It uses labels. // Default labels are: bug, duplicate, enhancement, invalid, question, wont fix // of those bug and enhancement are the ones that fit a type the best. // bug could be mapped to bug/issue in LeanKit and enhancement mapped to improvement/feature in LeanKit var defaultCardType = project.ValidCardTypes.FirstOrDefault(x => x.IsDefault); if (issue != null && issue.Labels != null && issue.Labels.Any()) { foreach (var label in issue.Labels) { var mappedWorkType = project.Types.FirstOrDefault(x => x.Target.ToLowerInvariant() == label.Name.ToLowerInvariant()); if (mappedWorkType != null) { var definedVal = project.ValidCardTypes.FirstOrDefault(x => x.Name.ToLowerInvariant() == mappedWorkType.LeanKit.ToLowerInvariant()); if (definedVal != null) { return definedVal; } } var implicitVal = project.ValidCardTypes.FirstOrDefault(x => x.Name.ToLowerInvariant() == label.Name.ToLowerInvariant()); if (implicitVal != null) { return implicitVal; } } } return defaultCardType; }
protected override void OnStartFixture() { _testBoard = Test <Board> .Item; int x = 0; foreach (var lane in _testBoard.Lanes) { lane.Active = true; lane.ClassType = LaneClassType.Active; lane.Index = x++; } // assign an arbitrary archive lane as the top level archive var lastArchiveItem = _testBoard.Archive.Last(); lastArchiveItem.ParentLaneId = 0; _testBoard.ArchiveTopLevelLaneId = lastArchiveItem.Id; activeLaneCount = _testBoard.Lanes.Count; _mapping = Test <BoardMapping> .Item; _mapping.Identity.LeanKit = _testBoard.Id; TestConfig = Test <Configuration> .Item; TestConfig.Mappings = new List <BoardMapping> { _mapping }; }
public static CardType CalculateLeanKitCardType(BoardMapping project, string issueTypeName) { var boardId = project.Identity.LeanKit; if (!string.IsNullOrEmpty(issueTypeName)) { var mappedWorkType = project.Types.FirstOrDefault(x => x.Target.ToLowerInvariant() == issueTypeName.ToLowerInvariant()); if (mappedWorkType != null) { var definedVal = project.ValidCardTypes.FirstOrDefault(x => x.Name.ToLowerInvariant() == mappedWorkType.LeanKit.ToLowerInvariant()); if (definedVal != null) { return(definedVal); } } var implicitVal = project.ValidCardTypes.FirstOrDefault(x => x.Name.ToLowerInvariant() == issueTypeName.ToLowerInvariant()); if (implicitVal != null) { return(implicitVal); } } return(project.ValidCardTypes.FirstOrDefault(x => x.IsDefault)); }
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); } }
void Start() { br = GameObject.FindObjectOfType(typeof(BoardRules)) as BoardRules; ps = GameObject.FindObjectOfType(typeof(PlayerScript)) as PlayerScript; bm = GameObject.FindObjectOfType(typeof(BoardMapping)) as BoardMapping; cor = "Black"; cor_adv = "White"; //receber como parametro cor escolhida pelo jogador }
protected override void OnStartFixture() { _testBoard = Test <Board> .Item; _mapping = Test <BoardMapping> .Item; _mapping.Identity.LeanKit = _testBoard.Id; TestConfig = Test <Configuration> .Item; TestConfig.Mappings = new List <BoardMapping> { _mapping }; }
protected override void OnStartFixture() { _testBoard = Test <Board> .Item; _testBoard.ArchiveTopLevelLaneId = null; _testBoard.Archive[0].ParentLaneId = 0; _mapping = Test <BoardMapping> .Item; _mapping.Identity.LeanKit = _testBoard.Id; TestConfig = Test <Configuration> .Item; TestConfig.Mappings = new List <BoardMapping> { _mapping }; }
public static CardType CalculateLeanKitCardType(BoardMapping project, string issueTypeName) { if (string.IsNullOrEmpty(issueTypeName)) return project.ValidCardTypes.FirstOrDefault(x => x.IsDefault); var mappedWorkType = project.Types.FirstOrDefault(x => x.Target.Equals(issueTypeName, StringComparison.InvariantCultureIgnoreCase)); if (mappedWorkType != null) { var definedVal = project.ValidCardTypes.FirstOrDefault(x => x.Name.Equals(mappedWorkType.LeanKit, StringComparison.InvariantCultureIgnoreCase)); if (definedVal != null) return definedVal; } var implicitVal = project.ValidCardTypes.FirstOrDefault(x => x.Name.Equals(issueTypeName, StringComparison.InvariantCultureIgnoreCase)); return implicitVal ?? project.ValidCardTypes.FirstOrDefault(x => x.IsDefault); }
// Use this for initialization void Start() { gm = GameObject.FindObjectOfType(typeof(GameManager)) as GameManager; bm = GameObject.FindObjectOfType(typeof(BoardMapping)) as BoardMapping; br = GameObject.FindObjectOfType(typeof(BoardRules)) as BoardRules; piece = null; pieceToMove = null; tile = null; ctrlMove = false; podeJogar = false; podeMudarTurno = true; }
protected override void OnStartFixture() { _testBoard = Test <Board> .Item; foreach (var cardType in _testBoard.CardTypes) { cardType.IsDefault = false; } _testBoard.CardTypes.Last().IsDefault = true; _mapping = Test <BoardMapping> .Item; _mapping.Identity.LeanKit = _testBoard.Id; TestConfig = Test <Configuration> .Item; TestConfig.Mappings = new List <BoardMapping> { _mapping }; }
protected override void OnStartFixture() { _testBoard = Test <Board> .Item; _testBoard.ArchiveTopLevelLaneId = null; var archiveLane = _testBoard.AllLanes().FirstOrDefault(x => x.ClassType == LaneClassType.Archive); archiveLane.ParentLaneId = 0; _mapping = Test <BoardMapping> .Item; _mapping.Identity.LeanKit = _testBoard.Id; _mapping.ArchiveLaneId = 0; TestConfig = Test <Configuration> .Item; TestConfig.Mappings = new List <BoardMapping> { _mapping }; }
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); } }
protected override void OnStartFixture() { _testBoard = Test <Board> .Item; _mapping = Test <BoardMapping> .Item; _testCardAddResult1 = Test <CardAddResult> .Item; _testCardAddResult1.CardId = 1; _mapping.Identity.LeanKit = _testBoard.Id; _mapping.LaneToStatesMap.Add(1, new List <string> { "open" }); TestConfig = Test <Configuration> .Item; TestConfig.PollingFrequency = 5000; TestConfig.Mappings = new List <BoardMapping> { _mapping }; }
protected override void OnStartFixture() { _testBoard = Test <Board> .Item; _mapping = Test <BoardMapping> .Item; _mapping.Identity.LeanKit = _testBoard.Id; _mapping.LaneToStatesMap.Add(1, new List <string> { "open" }); _mapping.LaneToStatesMap.Add(2, new List <string> { "accepted>resolved>closed" }); TestConfig = Test <Configuration> .Item; TestConfig.Mappings = new List <BoardMapping> { _mapping }; }
protected override void OnStartFixture() { _testBoard = Test <Board> .Item; allLaneCount = _testBoard.AllLanes().Count(x => x.ClassType != LaneClassType.Archive); // insure at least 1 active lane for testing if (allLaneCount == 0) { _testBoard.Lanes[0].ClassType = LaneClassType.Active; allLaneCount = 1; } _mapping = Test <BoardMapping> .Item; _mapping.Identity.LeanKit = _testBoard.Id; TestConfig = Test <Configuration> .Item; TestConfig.Mappings = new List <BoardMapping> { _mapping }; }
public static CardType CalculateLeanKitCardType(BoardMapping project, string issueTypeName) { var boardId = project.Identity.LeanKit; if (!string.IsNullOrEmpty(issueTypeName)) { var mappedWorkType = project.Types.FirstOrDefault(x => x.Target.ToLowerInvariant() == issueTypeName.ToLowerInvariant()); if (mappedWorkType != null) { var definedVal = project.ValidCardTypes.FirstOrDefault(x => x.Name.ToLowerInvariant() == mappedWorkType.LeanKit.ToLowerInvariant()); if (definedVal != null) { return definedVal; } } var implicitVal = project.ValidCardTypes.FirstOrDefault(x => x.Name.ToLowerInvariant() == issueTypeName.ToLowerInvariant()); if (implicitVal != null) { return implicitVal; } } return project.ValidCardTypes.FirstOrDefault(x => x.IsDefault); }
protected override void OnStartFixture() { _testBoard = Test <Board> .Item; int ctr = 0; foreach (var boardUser in _testBoard.BoardUsers) { if (ctr == 0) { boardUser.UserName = "******"; boardUser.FullName = "Johnny Cash"; boardUser.EmailAddress = "*****@*****.**"; boardUser.Id = 101; } ctr++; } _mapping = Test <BoardMapping> .Item; _mapping.Identity.LeanKit = _testBoard.Id; TestConfig = Test <Configuration> .Item; TestConfig.Mappings = new List <BoardMapping> { _mapping }; }
private string GetJiraIssueType(BoardMapping boardMapping, long cardTypeId) { const string defaultIssueType = "Bug"; if (cardTypeId <= 0 || boardMapping == null || boardMapping.Types == null || boardMapping.ValidCardTypes == null || !boardMapping.ValidCardTypes.Any() || !boardMapping.Types.Any()) { return(defaultIssueType); } var lkType = boardMapping.ValidCardTypes.FirstOrDefault(x => x.Id == cardTypeId); if (lkType == null) { return(defaultIssueType); } var mappedType = boardMapping.Types.FirstOrDefault(x => x != null && !string.IsNullOrEmpty(x.LeanKit) && String.Equals(x.LeanKit, lkType.Name, StringComparison.OrdinalIgnoreCase)); return(mappedType != null ? mappedType.Target : "Bug"); }
protected override void OnStartFixture() { _testBoard = Test <Board> .Item; int x = 0; foreach (var lane in _testBoard.Lanes) { lane.Active = true; lane.ClassType = LaneClassType.Active; lane.ChildLaneIds = null; lane.ParentLaneId = 0; lane.Index = x++; } allLaneCount = _testBoard.AllLanes().Count(y => y.ClassType != LaneClassType.Archive); _mapping = Test <BoardMapping> .Item; _mapping.Identity.LeanKit = _testBoard.Id; _mapping.ArchiveLaneId = 0; _testBoard.ArchiveTopLevelLaneId = null; TestConfig = Test <Configuration> .Item; TestConfig.Mappings = new List <BoardMapping> { _mapping }; }
public static CardType LeanKitCardType(this Jira.Issue issue, BoardMapping project) { return CalculateLeanKitCardType(project, issue.Fields.IssueType.Name); }
protected override void UpdateStateOfExternalItem(Card card, List <string> states, BoardMapping boardMapping) { UpdateStateOfExternalItem(card, states, boardMapping, false); }
protected override void CardUpdated(Card updatedCard, List <string> updatedItems, BoardMapping boardMapping) { if (!updatedCard.ExternalSystemName.Equals(ServiceName, StringComparison.OrdinalIgnoreCase)) { return; } long issueNumber; string target = boardMapping.Identity.Target; // use external card id to get the GitHub Issue try { issueNumber = Convert.ToInt32(updatedCard.ExternalCardID.Split('|')[1]); } catch (Exception) { Log.Debug("Ignoring card [{0}] with missing external id value.", updatedCard.Id); return; } //"https://api.github.com/repos/{0}/{1}/issues/{2} var request = new RestRequest(string.Format("repos/{0}/{1}/issues/{2}", Configuration.Target.Host, target, issueNumber), Method.GET); var ghResp = _restClient.Execute(request); if (ghResp.StatusCode != HttpStatusCode.OK) { var serializer = new JsonSerializer <ErrorMessage>(); var errorMessage = serializer.DeserializeFromString(ghResp.Content); Log.Error(string.Format("Unable to get issue from GitHub, Error: {0}. Check your board mapping configuration.", errorMessage.Message)); } else { var issueToUpdate = new JsonSerializer <Issue>().DeserializeFromString(ghResp.Content); if (issueToUpdate != null && issueToUpdate.Number == issueNumber) { bool isDirty = false; if (updatedItems.Contains("Title") && issueToUpdate.Title != updatedCard.Title) { issueToUpdate.Title = updatedCard.Title; isDirty = true; } string updateJson = "{ \"title\": \"" + issueToUpdate.Title.Replace("\"", "\\\"") + "\""; if (updatedItems.Contains("Description") && issueToUpdate.Body.SanitizeCardDescription() != updatedCard.Description) { updateJson += ", \"body\": \"" + updatedCard.Description.Replace("\"", "\\\"") + "\""; isDirty = true; } if (updatedItems.Contains("Tags")) { var newLabels = updatedCard.Tags.Split(','); string updateLabels = ""; int ctr = 0; foreach (string newLabel in newLabels) { if (ctr > 0) { updateLabels += ", "; } updateLabels += "{ \"name\": \"" + newLabel.Trim() + "\"}"; ctr++; } updateJson += ", \"labels\": [" + updateLabels + "]"; isDirty = true; } updateJson += "}"; string comment = ""; if (updatedItems.Contains("Priority")) { comment += "LeanKit card Priority changed to " + updatedCard.Priority + ".<br />"; } if (updatedItems.Contains("DueDate")) { comment += "LeanKit card DueDate changed to " + updatedCard.DueDate + ".<br />"; } if (updatedItems.Contains("Size")) { comment += "LeanKit card Size changed to " + updatedCard.Size + ".<br />"; } if (updatedItems.Contains("Blocked")) { if (updatedCard.IsBlocked) { comment += "LeanKit card is blocked: " + updatedCard.BlockReason + ".<br />"; } else { comment += "LeanKit card is no longer blocked: " + updatedCard.BlockReason + ".<br />"; } } if (isDirty) { try { //"https://api.github.com/repos/{0}/{1}/issues/{2} var updateRequest = new RestRequest(string.Format("repos/{0}/{1}/issues/{2}", Configuration.Target.Host, target, issueNumber), Method.PATCH); updateRequest.AddParameter( "application/json", updateJson, ParameterType.RequestBody ); var resp = _restClient.Execute(updateRequest); if (resp.StatusCode != HttpStatusCode.OK) { var serializer = new JsonSerializer <ErrorMessage>(); var errorMessage = serializer.DeserializeFromString(resp.Content); Log.Error(string.Format("Unable to update Issue [{0}], Description: {1}, Message: {2}", issueNumber, resp.StatusDescription, errorMessage.Message)); } else { Log.Debug(String.Format("Updated Issue [{0}]", issueNumber)); } } catch (Exception ex) { Log.Error(string.Format("Unable to update Issue [{0}], Exception: {1}", issueNumber, ex.Message)); } } if (!string.IsNullOrEmpty(comment)) { try { //"https://api.github.com/repos/{0}/{1}/issues/{2}/comments var newCommentRequest = new RestRequest(string.Format("repos/{0}/{1}/issues/{2}/comments", Configuration.Target.Host, target, issueNumber), Method.POST); newCommentRequest.AddParameter( "application/json", "{ \"body\": \"" + comment + "\"}", ParameterType.RequestBody ); var resp = _restClient.Execute(newCommentRequest); 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 comment on updated Issue [{0}], Description: {1}, Message: {2}", issueNumber, resp.StatusDescription, errorMessage.Message)); } else { Log.Debug(String.Format("Created comment on Updated Issue [{0}]", issueNumber)); } } catch (Exception ex) { Log.Error(string.Format("Unable to create comment on updated Issue [{0}], Exception: {1}", issueNumber, ex.Message)); } } } } }
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 = CreateRequest("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 = ExecuteRequest(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.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); } }
private Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType GetTfsWorkItemType(BoardMapping boardMapping, WorkItemTypeCollection workItemTypes, long cardTypeId) { if (boardMapping != null && boardMapping.Types != null && boardMapping.ValidCardTypes != null && boardMapping.Types.Any() && boardMapping.ValidCardTypes.Any() ) { var lkType = boardMapping.ValidCardTypes.FirstOrDefault(x => x.Id == cardTypeId); if (lkType != null) { // first check for mapped type var mappedType = boardMapping.Types.FirstOrDefault(x => x.LeanKit.ToLowerInvariant() == lkType.Name.ToLowerInvariant()); if (mappedType != null) { if (workItemTypes.Contains(mappedType.Target)) return workItemTypes[mappedType.Target]; } // now check for implicit type if (workItemTypes.Contains(lkType.Name)) return workItemTypes[lkType.Name]; } } // else just return the first type from list of types from TFS return workItemTypes[0]; }
protected abstract void CardUpdated(Card card, List <string> updatedItems, BoardMapping boardMapping);
public void TestCardUpdated(Card card, List<string> updatedItems, BoardMapping boardMapping) { base.CardUpdated(card, updatedItems, boardMapping); }
private void CheckForMissedCardMoves(BoardMapping mapping) { if (!mapping.UpdateTargetItems) { Log.Info("Skipped check for missed card moves because 'UpdateTargetItems' is disabled."); return; } // if we have local storage, we have saved board versions and we have one for this board var boardId = mapping.Identity.LeanKit; if (AppSettings == null || AppSettings.BoardVersions == null || !AppSettings.BoardVersions.Any() || !AppSettings.BoardVersions.ContainsKey(boardId)) return; 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, version); var board = LeanKit.GetBoard(boardId); if (board == null || events == null) return; 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 OnStartFixture() { _testBoard = Test<Board>.Item; foreach (var cardType in _testBoard.CardTypes) cardType.IsDefault = false; _testBoard.CardTypes.Last().IsDefault = true; _mapping = Test<BoardMapping>.Item; _mapping.Identity.LeanKit = _testBoard.Id; TestConfig = Test<Configuration>.Item; TestConfig.Mappings = new List<BoardMapping> {_mapping}; }
protected override void OnStartFixture() { _testBoard = Test<Board>.Item; int ctr = 0; foreach (var boardUser in _testBoard.BoardUsers) { if (ctr == 0) { boardUser.UserName = "******"; boardUser.FullName = "Johnny Cash"; boardUser.EmailAddress = "*****@*****.**"; boardUser.Id = 101; } ctr++; } _mapping = Test<BoardMapping>.Item; _mapping.Identity.LeanKit = _testBoard.Id; TestConfig = Test<Configuration>.Item; TestConfig.Mappings = new List<BoardMapping> {_mapping}; }
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."); } }
protected abstract void Synchronize(BoardMapping boardMapping);
protected abstract void CreateNewItem(Card card, BoardMapping boardMapping);
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.LeanKitHtmlToJiraPlainText() + "\" "; 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 = CreateRequest("rest/api/latest/issue", Method.POST); createRequest.AddParameter("application/json", json, ParameterType.RequestBody); var resp = ExecuteRequest(createRequest); if (resp.StatusCode != HttpStatusCode.OK && resp.StatusCode != HttpStatusCode.Created) { Log.Error(string.Format("Unable to create Issue from card [{0}], Description: {1}, Message: {2}", card.ExternalCardID, resp.StatusDescription, resp.Content)); } 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)); } } }
protected abstract void CardUpdated(Card card, List<string> updatedItems, BoardMapping boardMapping);
protected override void CardUpdated(Card card, List<string> updatedItems, BoardMapping boardMapping) { if (!card.ExternalSystemName.Equals(ServiceName, StringComparison.OrdinalIgnoreCase)) return; if (string.IsNullOrEmpty(card.ExternalCardID)) return; Log.Info("Card [{0}] updated.", card.Id); int workItemId; try { workItemId = Convert.ToInt32(card.ExternalCardID); } catch (Exception) { Log.Debug("Ignoring card [{0}] with missing external id value.", card.Id); return; } Log.Debug("Attempting to load Work Item [{0}]", workItemId); WorkItem workItem; try { workItem = _projectCollectionWorkItemStore.GetWorkItem(workItemId); } catch (Exception ex) { Log.Error(ex, string.Format("Could not load Work Item [{0}]", workItemId)); return; } if (workItem == null) { Log.Debug("Failed to find work item matching [{0}].", workItemId); return; } if (updatedItems.Contains("Title") && workItem.Title != card.Title) workItem.Title = card.Title; if (updatedItems.Contains("Description")) { var description = workItem.LeanKitDescription(GetTfsVersion()); if (description != card.Description) { if (workItem.UseReproSteps()) workItem.Fields["Repro Steps"].Value = card.Description; else workItem.Description = card.Description; } } if (updatedItems.Contains("Priority")) { var currentWorkItemPriority = workItem.LeanKitPriority(); if (currentWorkItemPriority != card.Priority) SetWorkItemPriority(workItem, card.Priority); } if (updatedItems.Contains("DueDate")) { SetDueDate(workItem, card.DueDate); } if (workItem.IsDirty) { Log.Info("Updating corresponding work item [{0}]", workItem.Id); workItem.Save(); } // unsupported properties; append changes to history if (updatedItems.Contains("Size")) { workItem.History += "Card size changed to " + card.Size + "\r"; workItem.Save(); } if (updatedItems.Contains("Blocked")) { if (card.IsBlocked) workItem.History += "Card is blocked: " + card.BlockReason + "\r"; else workItem.History += "Card is no longer blocked: " + card.BlockReason + "\r"; workItem.Save(); } if (updatedItems.Contains("Tags")) { workItem.History += "Tags in LeanKit changed to " + card.Tags + "\r"; workItem.Save(); } }
protected abstract void Synchronize(BoardMapping boardMapping);
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); } }
protected override void OnStartFixture() { _testBoard = Test<Board>.Item; foreach (var cardType in _testBoard.CardTypes) cardType.IsDefault = false; _testBoard.CardTypes.Add(new CardType() { Id = 999, Name = "Willy", IsDefault = false }); _testBoard.CardTypes.Last().IsDefault = true; _mapping = Test<BoardMapping>.Item; _mapping.Identity.LeanKit = _testBoard.Id; _mapping.Types = new List<WorkItemType>() { new WorkItemType() { LeanKit = "Willy", Target = "Roger"}}; TestConfig = Test<Configuration>.Item; TestConfig.Mappings = new List<BoardMapping> { _mapping }; }
protected abstract void UpdateStateOfExternalItem(Card card, List<string> states, BoardMapping boardMapping);
protected override void OnStartFixture() { _testBoard = Test<Board>.Item; _mapping = Test<BoardMapping>.Item; _mapping.Identity.LeanKit = _testBoard.Id; _mapping.LaneToStatesMap.Add(1, new List<string> {"open"}); _mapping.LaneToStatesMap.Add(2, new List<string> {"closed"}); TestConfig = Test<Configuration>.Item; TestConfig.Mappings = new List<BoardMapping> {_mapping}; }
protected abstract void CreateNewItem(Card card, BoardMapping boardMapping);
public void Syncronize(BoardMapping boardConfig) { base.Synchronize(boardConfig); }
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."); } }
protected override void OnStartFixture() { _testBoard = Test<Board>.Item; _mapping = Test<BoardMapping>.Item; _testCardAddResult1 = Test<CardAddResult>.Item; _testCardAddResult1.CardId = 1; _mapping.Identity.LeanKit = _testBoard.Id; _mapping.LaneToStatesMap.Add(1, new List<string> {"open"}); TestConfig = Test<Configuration>.Item; TestConfig.PollingFrequency = 5000; TestConfig.Mappings = new List<BoardMapping> {_mapping}; }
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().Replace(";", ","); } 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); }
public static CardType LeanKitCardType(this Unfuddle.Ticket ticket, BoardMapping project) { return(CalculateLeanKitCardType(project, "")); }
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 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().JiraPlainTextToLeanKitHtml(), 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 void UpdateStateOfExternalItem(Card card, List <string> states, BoardMapping boardMapping, bool runOnlyOnce) { if (!card.ExternalSystemName.Equals(ServiceName, StringComparison.OrdinalIgnoreCase)) { return; } if (string.IsNullOrEmpty(card.ExternalCardID)) { return; } if (states == null || states.Count == 0) { return; } long issueNumber; string target = boardMapping.Identity.Target; // use external card id to get the GitHub issue try { issueNumber = Convert.ToInt32(card.ExternalCardID.Split('|')[1]); } catch (Exception) { Log.Debug("Ignoring card [{0}] with missing external id value.", card.Id); return; } int tries = 0; bool success = false; while (tries < 10 && !success && (!runOnlyOnce || tries == 0)) { if (tries > 0) { Log.Error(string.Format("Attempting to update external issue [{0}] attempt number [{1}]", issueNumber, tries)); // wait 5 seconds before trying again Thread.Sleep(new TimeSpan(0, 0, 5)); } //"https://api.github.com/repos/{0}/{1}/issues/{2} var request = new RestRequest(string.Format("repos/{0}/{1}/issues/{2}", Configuration.Target.Host, target, issueNumber), Method.GET); var ghResp = _restClient.Execute(request); if (ghResp.StatusCode != HttpStatusCode.OK) { var serializer = new JsonSerializer <ErrorMessage>(); var errorMessage = serializer.DeserializeFromString(ghResp.Content); Log.Error(string.Format("Unable to get issue from GitHub, Error: {0}. Check your board mapping configuration.", errorMessage.Message)); } else { var issueToUpdate = new JsonSerializer <Issue>().DeserializeFromString(ghResp.Content); if (issueToUpdate != null && issueToUpdate.Number == issueNumber) { if (issueToUpdate.State.ToLowerInvariant() == states[0].ToLowerInvariant()) { Log.Debug(string.Format("Issue [{0}] is already in state [{1}]", issueToUpdate.Id, states[0])); return; } issueToUpdate.State = states[0]; try { //"https://api.github.com/repos/{0}/{1}/issues/{2} var updateRequest = new RestRequest(string.Format("repos/{0}/{1}/issues/{2}", Configuration.Target.Host, target, issueNumber), Method.PATCH); updateRequest.AddParameter("application/json", "{ \"state\": \"" + issueToUpdate.State + "\"}", ParameterType.RequestBody); var resp = _restClient.Execute(updateRequest); if (resp.StatusCode != HttpStatusCode.OK) { var serializer = new JsonSerializer <ErrorMessage>(); var errorMessage = serializer.DeserializeFromString(resp.Content); Log.Error(string.Format("Unable to update Issue [{0}] to [{1}], Description: {2}, Message: {3}", issueNumber, issueToUpdate.State, resp.StatusDescription, errorMessage.Message)); } else { success = true; Log.Debug(String.Format("Updated state for Issue [{0}] to [{1}]", issueNumber, issueToUpdate.State)); } } catch (Exception ex) { Log.Error(string.Format("Unable to update Issue [{0}] to [{1}], Exception: {2}", issueNumber, issueToUpdate.State, ex.Message)); } } else { Log.Debug(String.Format("Could not retrieve Issue [{0}] for updating state to [{1}]", issueNumber, issueToUpdate.State)); } } tries++; } }
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.Contains(',') ? tfsTags.Split(',') : tfsTags.Split(';'); foreach (var tag in tfsTagsArr) { if (card.Tags != null && card.Tags.ToLowerInvariant().Contains(tag.ToLowerInvariant())) continue; if (string.IsNullOrEmpty(card.Tags)) 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"); } } }
protected override void CreateNewItem(Card card, BoardMapping boardMapping) { Log.Debug(String.Format("TODO: Create an Issue from Card [{0}]", card.Id)); }
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); }
protected override void UpdateStateOfExternalItem(Card card, List<string> states, BoardMapping boardMapping) { UpdateStateOfExternalItem(card, states, boardMapping, false); }
protected void UpdateStateOfExternalItem(Card card, List<string> states, BoardMapping mapping, bool runOnlyOnce) { if (string.IsNullOrEmpty(card.ExternalSystemName) || !card.ExternalSystemName.Equals(ServiceName, StringComparison.OrdinalIgnoreCase)) return; if (string.IsNullOrEmpty(card.ExternalCardID)) { Log.Debug("Ignoring card [{0}] with missing external id value.", card.Id); return; } if (states == null || states.Count == 0) return; int tries = 0; bool success = false; while (tries < 10 && !success && (!runOnlyOnce || tries == 0)) { if (tries > 0) { Log.Warn(string.Format("Attempting to update external work item [{0}] attempt number [{1}]", card.ExternalCardID, tries)); // wait 5 seconds before trying again Thread.Sleep(new TimeSpan(0, 0, 5)); } //https://yoursite.atlassian.net/rest/api/latest/issue/{issueIdOrKey} var request = CreateRequest(string.Format("rest/api/latest/issue/{0}", card.ExternalCardID), Method.GET); var jiraResp = ExecuteRequest(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/repo mapping configuration.", errorMessage.Message)); } else { var issueToUpdate = new JsonSerializer<Issue>().DeserializeFromString(jiraResp.Content); // Check for a workflow mapping to the closed state if (states != null && states.Count > 0 && states[0].Contains(">")) { var workflowStates = states[0].Split('>'); // check to see if the workitem is already in one of the workflow states var alreadyInState = workflowStates.FirstOrDefault( x => x.Trim().ToLowerInvariant() == issueToUpdate.Fields.Status.Name.ToLowerInvariant()); if (!string.IsNullOrEmpty(alreadyInState)) { // change workflowStates to only use the states after the currently set state var currentIndex = Array.IndexOf(workflowStates, alreadyInState); if (currentIndex < workflowStates.Length - 1) { var updatedWorkflowStates = new List<string>(); for (int i = currentIndex + 1; i < workflowStates.Length; i++) { updatedWorkflowStates.Add(workflowStates[i]); } workflowStates = updatedWorkflowStates.ToArray(); } } if (workflowStates.Length > 0) { foreach (string workflowState in workflowStates) { UpdateStateOfExternalItem(card, new List<string> {workflowState.Trim()}, mapping, runOnlyOnce); } return; } } foreach (var state in states) { if (issueToUpdate.Fields.Status.Name.ToLowerInvariant() == state.ToLowerInvariant()) { Log.Debug(string.Format("Issue [{0}] is already in state [{1}]", issueToUpdate.Key, state)); return; } } try { // first get a list of available transitions var transitionsRequest = CreateRequest( string.Format("rest/api/2/issue/{0}/transitions?expand=transitions.fields", card.ExternalCardID), Method.GET); var transitionsResponse = ExecuteRequest(transitionsRequest); if (transitionsResponse.StatusCode != HttpStatusCode.OK) { var serializer = new JsonSerializer<ErrorMessage>(); var errorMessage = serializer.DeserializeFromString(jiraResp.Content); Log.Error(string.Format("Unable to get available transitions from Jira, Error: {0}.", errorMessage.Message)); } else { var availableTransitions = new JsonSerializer<TransitionsResponse>().DeserializeFromString( transitionsResponse.Content); if (availableTransitions != null && availableTransitions.Transitions != null && availableTransitions.Transitions.Any()) { // now find match from available transitions to states var valid = false; Transition validTransition = null; foreach (var st in states) { validTransition = availableTransitions.Transitions.FirstOrDefault( x => x.Name.ToLowerInvariant() == st.ToLowerInvariant() || x.To.Name.ToLowerInvariant() == st.ToLowerInvariant()); if (validTransition != null) { // if you find one then set it valid = true; break; } } if (!valid) { // if not then write an error message Log.Error( string.Format( "Unable to update Issue [{0}] to [{1}] because the status transition is invalid. Try adding additional states to the config.", card.ExternalCardID, states.Join(","))); } else { // go ahead and try to update the state of the issue in JIRA //https://yoursite.atlassian.net/rest/api/latest/issue/{issueIdOrKey}/transitions?expand=transitions.fields var updateRequest = CreateRequest( string.Format( "rest/api/latest/issue/{0}/transitions?expand=transitions.fields", card.ExternalCardID), Method.POST); updateRequest.AddParameter("application/json", "{ \"transition\": { \"id\": \"" + validTransition.Id + "\"}}", ParameterType.RequestBody); var resp = ExecuteRequest(updateRequest); if (resp.StatusCode != HttpStatusCode.OK && resp.StatusCode != HttpStatusCode.NoContent) { var serializer = new JsonSerializer<ErrorMessage>(); var errorMessage = serializer.DeserializeFromString(resp.Content); Log.Error( string.Format( "Unable to update Issue [{0}] to [{1}], Description: {2}, Message: {3}", card.ExternalCardID, validTransition.To.Name, resp.StatusDescription, errorMessage.Message)); } else { success = true; Log.Debug(String.Format("Updated state for Issue [{0}] to [{1}]", card.ExternalCardID, validTransition.To.Name)); } } } else { Log.Error( string.Format( "Unable to update Issue [{0}] to [{1}] because no transitions were available from its current status [{2}]. The user account you are using to connect may not have proper privileges.", card.ExternalCardID, states.Join(","), issueToUpdate.Fields.Status.Name)); } } } catch (Exception ex) { Log.Error(string.Format("Unable to update Issue [{0}] to [{1}], Exception: {2}", card.ExternalCardID, states.Join(","), ex.Message)); } } tries++; } }
public void TestUpdateStateOfExternalItem(Card card, List<string> laneStateMap, BoardMapping boardConfig) { base.UpdateStateOfExternalItem(card, laneStateMap, boardConfig, true); }
protected void UpdateStateOfExternalItem(Card card, List<string> states, BoardMapping mapping, bool runOnlyOnce) { if (!mapping.UpdateTargetItems) return; if (!card.ExternalSystemName.Equals(ServiceName, StringComparison.OrdinalIgnoreCase)) return; if (string.IsNullOrEmpty(card.ExternalCardID)) return; int workItemId; // use external card id to get the TFS work item try { workItemId = Convert.ToInt32(card.ExternalCardID); } catch (Exception) { Log.Debug("Ignoring card [{0}] with missing external id value.", card.Id); return; } if (states == null || states.Count == 0) return; var tries = 0; var success = false; while (tries < 10 && !success && (!runOnlyOnce || tries == 0)) { if (tries > 0) { Log.Warn(String.Format("Attempting to update external work item [{0}] attempt number [{1}]", workItemId, tries)); // wait 5 seconds before trying again Thread.Sleep(new TimeSpan(0, 0, 5)); } Log.Debug("Attempting to retrieve work item [{0}]", workItemId); var workItemToUpdate = _projectCollectionWorkItemStore.GetWorkItem(workItemId); if (workItemToUpdate != null) { var initialState = workItemToUpdate.State; // iterate through the configured states until we find the one that works. // Alternately we could do something with the validation results and check AllowedStates // may be able to figure out the issue and handle it // see http://bartwullems.blogspot.com/2012/04/tf237124-work-item-is-not-ready-to-save.html // according to docs the result should be a collection of Microsoft.TeamFoundation.WorkItemTracking.Client, not sure why it is an ArrayList var ctr = 0; var valid = false; foreach (var st in states) { if (ctr > 0) workItemToUpdate = _projectCollectionWorkItemStore.GetWorkItem(workItemId); var attemptState = st; // Check for a workflow mapping to the closed state if (attemptState.Contains(">")) { var workflowStates = attemptState.Split('>'); // check to see if the workitem is already in one of the workflow states var alreadyInState = workflowStates.FirstOrDefault(x => x.Trim().ToLowerInvariant() == workItemToUpdate.State.ToLowerInvariant()); if (!string.IsNullOrEmpty(alreadyInState)) { // change workflowStates to only use the states after the currently set state var currentIndex = Array.IndexOf(workflowStates, alreadyInState); if (currentIndex < workflowStates.Length - 1) { var updatedWorkflowStates = new List<string>(); for (var i = currentIndex + 1; i < workflowStates.Length; i++) { updatedWorkflowStates.Add(workflowStates[i]); } workflowStates = updatedWorkflowStates.ToArray(); } } // UpdateStateOfExternalItem(card, new List<string>() { workflowState.Trim() }, mapping, runOnlyOnce); if (workflowStates.Length > 0) { // if it is already in the final state then bail if (workItemToUpdate.State.ToLowerInvariant() == workflowStates.Last().ToLowerInvariant()) { Log.Debug("WorkItem [{0}] is already in state [{1}]", workItemId, workItemToUpdate.State); return; } // check to make sure that the first state is valid // if it is then update the work item through the workflow // if not then just process the states from the config file as normal workItemToUpdate.State = workflowStates[0]; // check to see if the work item is already in any of the states // if so then skip those states if (workItemToUpdate.IsValid()) { Log.Debug("Attempting to process WorkItem [{0}] through workflow of [{1}].", workItemId, attemptState); foreach (var workflowState in workflowStates) { UpdateStateOfExternalItem(card, new List<string> {workflowState.Trim()}, mapping, runOnlyOnce); } // Get the work item again and check the current state. // if it is not in the last state of the workflow then something went wrong // so reverse the updates we made and it will then try the next configuration workflow if there is one var updatedWorkItem = _projectCollectionWorkItemStore.GetWorkItem(workItemId); if (updatedWorkItem != null) { if (updatedWorkItem.State.ToLowerInvariant() == workflowStates.Last().ToLowerInvariant()) { return; } // try to reverse the changes we've made Log.Debug("Attempted invalid workflow for WorkItem [{0}]. Attempting to reverse previous state changes.", workItemId); foreach (var workflowState in workflowStates.Reverse().Skip(1)) { UpdateStateOfExternalItem(card, new List<string> {workflowState.Trim()}, mapping, runOnlyOnce); } // now try to set it back whatever it was before Log.Debug("Attempted invalid workflow for WorkItem [{0}]. Setting state back to initial state of [{1}].", workItemId, initialState); UpdateStateOfExternalItem(card, new List<string> {initialState.Trim()}, mapping, runOnlyOnce ); // set the current attempt to empty string so that it will not be valid and // we'll try the next state (or workflow) attemptState = ""; } } } } if (workItemToUpdate.State.Equals(attemptState, StringComparison.InvariantCultureIgnoreCase)) { Log.Debug(string.Format("WorkItem [{0}] is already in state [{1}]", workItemId, workItemToUpdate.State)); return; } if (!string.IsNullOrEmpty(attemptState)) { workItemToUpdate.State = attemptState; valid = workItemToUpdate.IsValid(); } ctr++; if (valid) break; } if (!valid) { Log.Warn("Unable to update WorkItem [{0}] to [{1}] because the state is invalid from the current state.", workItemId, workItemToUpdate.State); return; } try { workItemToUpdate.Save(); success = true; Log.Debug("Updated state for mapped WorkItem [{0}] to [{1}]", workItemId, workItemToUpdate.State); } catch (ValidationException ex) { Log.Warn("Unable to update WorkItem [{0}] to [{1}], ValidationException: {2}", workItemId, workItemToUpdate.State, ex.Message); } catch (Exception ex) { Log.Error(ex, string.Format("Unable to update WorkItem [{0}] to [{1}]", workItemId, workItemToUpdate.State)); } } else { Log.Debug("Could not retrieve WorkItem [{0}] for updating state to [{1}]", workItemId, workItemToUpdate.State); } tries++; } }