Esempio n. 1
0
		public Card ToCard()
		{
			var card = new Card();
			card.Active = Active;
			card.ClassOfServiceId = ClassOfServiceId;
			card.BlockReason = BlockReason;
			card.Description = Description;
			card.StartDate = StartDate;
			card.DueDate = DueDate;
			card.ExternalCardID = ExternalCardID;
			card.ExternalSystemName = ExternalSystemName;
			card.ExternalSystemUrl = ExternalSystemUrl;
			card.Id = Id;
			card.Index = Index;
			card.IsBlocked = IsBlocked;
			card.LaneId = LaneId;
			card.Priority = (int) Priority;
			card.Size = Size;
			card.Tags = Tags;
			card.Title = Title;
			card.TypeId = TypeId;
			card.Version = Version;
			card.AssignedUserIds = (AssignedUsers != null) ? AssignedUsers.Select(x => x.AssignedUserId).ToArray() : null;
			card.Comments = Comments;
			card.HistoryEvents = HistoryEvents;
			card.HistoryEvents = HistoryEvents;
			card.LastMove = LastMove;
			card.LastActivity = LastActivity;
			card.LastComment = LastComment;
			card.DateArchived = DateArchived;
			return card;
		}
		public void It_should_not_call_unfuddle_to_update_ticket_for_states_it_is_in_or_past() 
		{
			Card card2 = new Card() { Id = 2, ExternalSystemName = "Unfuddle", ExternalCardID = "2" };
			Card card4 = new Card() { Id = 4, ExternalSystemName = "Unfuddle", ExternalCardID = "4" };

			((TestUnfuddle)TestItem).TestUpdateStateOfExternalItem(card2, _mapping.LaneToStatesMap[2], _mapping);
			((TestUnfuddle)TestItem).TestUpdateStateOfExternalItem(card4, _mapping.LaneToStatesMap[2], _mapping);

			MockRestClient.Verify(x => x.Execute(It.Is<RestRequest>(y => y.Resource.Contains("tickets/2") && y.Method == Method.GET)), Times.Exactly(3));
			MockRestClient.Verify(x => x.Execute(It.Is<RestRequest>(y => y.Resource.Contains("tickets/2") && y.Method == Method.PUT)), Times.Exactly(2));

			MockRestClient.Verify(x => x.Execute(It.Is<RestRequest>(y => y.Resource.Contains("tickets/4") && y.Method == Method.GET)), Times.Exactly(2));
			MockRestClient.Verify(x => x.Execute(It.Is<RestRequest>(y => y.Resource.Contains("tickets/4") && y.Method == Method.PUT)), Times.Exactly(1));	
		}
		public void AddTaskboardCard(Card card, long taskboardId, string wipOverrideReason)
		{
			//var results = string.IsNullOrEmpty(wipOverrideReason)
			//	? _api.AddTaskboardCard(_boardId, taskboardId, card)
			//	: _api.AddTaskboardCard(_boardId, taskboardId, card, wipOverrideReason);

			//_boardLock.EnterWriteLock();
			//try {
			//	//TODO:  Figure out what to do for taskboards
			//	//ApplyBoardChanges(results.BoardVersion, new[] {results.Lane});
			//} finally {
			//	_boardLock.ExitWriteLock();
			//}
		}
		public void UpdateTask(Card task, long cardId, string wipOverrideReason)
		{
			//var results = string.IsNullOrEmpty(wipOverrideReason)
			//	? _api.UpdateTask(_boardId, cardId, task)
			//	: _api.UpdateTask(_boardId, cardId, task, wipOverrideReason);

			//TODO: Figure out how to handle taskboards
			//            CardView cardView = results.CardDTO;
			//            Lane lane = _board.GetLaneById(cardView.LaneId);
			//
			//TODO: handle the situation where a card in moved through the Update method
			//
			//            _boardLock.EnterWriteLock();
			//            try {
			//                lane.UpdateCard(cardView);
			//                ApplyBoardChanges(results.BoardVersion, new[] { lane });
			//            }
			//            finally {
			//                _boardLock.ExitWriteLock();
			//            }
		}
		public void AddTask(Card task, long cardId)
		{
			AddTask(task, cardId, string.Empty);
		}
		public virtual void UpdateCard(Card card)
		{
			UpdateCard(card, string.Empty);
		}
		public virtual void AddCard(Card card)
		{
			AddCard(card, string.Empty);
		}
 protected abstract void CardUpdated(Card card, List<string> updatedItems, BoardMapping boardMapping);
		public void It_should_not_call_github_to_update_pull_if_externalsystemname_does_not_match()
		{
			Card card = new Card() {Id = 4, ExternalSystemName = "GitHubby", ExternalCardID = "4|4"};
			((TestGitHubPulls) TestItem).TestUpdateStateOfExternalItem(card, _mapping.LaneToStatesMap[2], _mapping);
			MockRestClient.Verify(
				x => x.Execute(It.Is<RestRequest>(y => y.Resource.Contains("pulls/4") && y.Method == Method.GET)), Times.Never());
			MockRestClient.Verify(
				x => x.Execute(It.Is<RestRequest>(y => y.Resource.Contains("pulls/4") && y.Method == Method.PATCH)), Times.Never());
		}
		public void It_should_not_call_github_to_update_pull_state_is_already_end_state()
		{
			Card card = new Card() {Id = 2, ExternalSystemName = "GitHub", ExternalCardID = "2|2"};
			((TestGitHubPulls) TestItem).TestUpdateStateOfExternalItem(card, _mapping.LaneToStatesMap[2], _mapping);
			MockRestClient.Verify(
				x => x.Execute(It.Is<RestRequest>(y => y.Resource.Contains("pulls/2") && y.Method == Method.GET)), Times.Exactly(1));
			MockRestClient.Verify(
				x => x.Execute(It.Is<RestRequest>(y => y.Resource.Contains("pulls/2") && y.Method == Method.PATCH)), Times.Never());
		}
			public void TestUpdateStateOfExternalItem(Card card, List<string> laneStateMap, BoardMapping boardConfig)
			{
				base.UpdateStateOfExternalItem(card, laneStateMap, boardConfig, true);
			}
		public void It_should_not_call_github_to_update_pull_if_externalsystemname_is_different()
		{
			Card card = new Card();
			card.ExternalCardID = "5|5";
			card.ExternalSystemName = "GitHubby";
			card.Description = "Pull 5";
			card.Title = "Pull 5";

			((TestGitHubPulls) TestItem).TestCardUpdated(card, new List<string>(), _mapping);
			MockRestClient.Verify(
				x => x.Execute(It.Is<RestRequest>(y => y.Resource.Contains("pulls/5") && y.Method == Method.GET)), Times.Never());
			MockRestClient.Verify(
				x => x.Execute(It.Is<RestRequest>(y => y.Resource.Contains("pulls/5") && y.Method == Method.PATCH)), Times.Never());
		}
		public void It_should_not_call_github_to_update_pull_if_no_identified_properties_change()
		{
			Card card = new Card();
			card.ExternalCardID = "4|4";
			card.ExternalSystemName = "GitHub";
			card.Description = "Pull 4";
			card.Title = "Pull 4";

			((TestGitHubPulls) TestItem).TestCardUpdated(card, new List<string>(), _mapping);
			MockRestClient.Verify(
				x => x.Execute(It.Is<RestRequest>(y => y.Resource.Contains("pulls/4") && y.Method == Method.GET)), Times.Exactly(1));
			MockRestClient.Verify(
				x => x.Execute(It.Is<RestRequest>(y => y.Resource.Contains("pulls/4") && y.Method == Method.PATCH)), Times.Never());
		}
			public void TestCardUpdated(Card card, List<string> updatedItems, BoardMapping boardMapping)
			{
				base.CardUpdated(card, updatedItems, boardMapping);
			}
		public void It_should_not_call_unfuddle_to_update_ticket_if_externalstatename_does_not_match() 
		{
			Card card = new Card() { Id = 5, ExternalSystemName = "Unfuddlest", ExternalCardID = "5" };
			((TestUnfuddle)TestItem).TestUpdateStateOfExternalItem(card, _mapping.LaneToStatesMap[2], _mapping);

			MockRestClient.Verify(x => x.Execute(It.Is<RestRequest>(y => y.Resource.Contains("tickets/5") && y.Method == Method.GET)), Times.Never());
			MockRestClient.Verify(x => x.Execute(It.Is<RestRequest>(y => y.Resource.Contains("tickets/5") && y.Method == Method.PUT)), Times.Never());
		}
		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 abstract void UpdateStateOfExternalItem(Card card, List<string> states, BoardMapping boardMapping);
		public void CallWillUpdateCard()
		{
			Board board = GetSampleBoard();
			_apiMock.Expect(x => x.GetBoard(1)).Return(board);
			Card cardToUpdate = new Card
			{
				Id = 1,
				Title = "Card 1 Updated",
				LaneId = 1,
				Description = "some desc 1",
				TypeId = 1,
				ExternalCardID = "123"
			};
			CardUpdateResult result = new CardUpdateResult {BoardVersion = 1, CardDTO = cardToUpdate.ToCardView()};

			_apiMock.Expect(x => x.UpdateCard(Arg<long>.Is.Anything, Arg<Card>.Is.Anything)).Return(result);

			_integration = new LeanKitIntegration(1, _apiMock);
			_integration.ShouldContinue = false;
			_integration.StartWatching();


			_integration.UpdateCard(cardToUpdate);
			Card card = _integration.GetCard(1);
			Assert.NotNull(card);
			Assert.AreEqual("Card 1 Updated", card.Title);
		}
	    protected abstract void CreateNewItem(Card card, BoardMapping boardMapping);
		public void CallWillAddCard()
		{
			Board board = GetSampleBoard();
			_apiMock.Expect(x => x.GetBoard(1)).Return(board);
			Card cardToAdd = new Card
			{
				Id = 1,
				Title = "Card 1 Updated",
				LaneId = 1,
				Description = "some desc 1",
				TypeId = 1,
				ExternalCardID = "123"
			};
			Lane affectedLane = new Lane
			{
				Id = 1,
				Title = "Lane 1",
				Cards = new List<CardView>
				{
					new CardView
					{
						Id = 1,
						Title = "Card 1",
						LaneId = 1,
						Description = "some desc 1",
						Type = new CardType {Id = 1, Name = "Card type 1", IconPath = @"C:\"}
					},
					new CardView
					{
						Id = 2,
						Title = "Card 2",
						LaneId = 1,
						Description = "some desc 2",
						Type = new CardType {Id = 1, Name = "Card type 1", IconPath = @"C:\"}
					},
					new CardView
					{
						Id = 4,
						Title = "Card 4",
						LaneId = 1,
						Description = "some desc 4",
						Type = new CardType {Id = 1, Name = "Card type 1", IconPath = @"C:\"}
					},
				},
			};
			CardAddResult result = new CardAddResult {BoardVersion = 1, CardId = 4, Lane = affectedLane};

			_apiMock.Expect(x => x.AddCard(Arg<long>.Is.Anything, Arg<Card>.Is.Anything)).Return(result);

			_integration = new LeanKitIntegration(1, _apiMock);
			_integration.ShouldContinue = false;
			_integration.StartWatching();

			_integration.AddCard(cardToAdd);

			Card card = _integration.GetCard(4);
			Assert.NotNull(card);
			Assert.AreEqual("Card 4", card.Title);
		}
		public void AddCard(Card card, string wipOverrideReason)
		{
			var results = string.IsNullOrEmpty(wipOverrideReason)
				? _api.AddCard(_boardId, card)
				: _api.AddCard(_boardId, card, wipOverrideReason);

			_boardLock.EnterWriteLock();
			try
			{
				ApplyBoardChanges(results.BoardVersion, new[] {results.Lane});
			}
			finally
			{
				_boardLock.ExitWriteLock();
			}
		}
        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 virtual void UpdateCard(Card card, string wipOverrideReason)
		{
			var results = string.IsNullOrEmpty(wipOverrideReason)
				? _api.UpdateCard(_boardId, card)
				: _api.UpdateCard(_boardId, card, wipOverrideReason);
			var cardView = results.CardDTO;
			var lane = _board.GetLaneById(cardView.LaneId);

			//TODO: handle the situation where a card in moved through the Update method

			_boardLock.EnterWriteLock();
			try
			{
				lane.UpdateCard(cardView);
				ApplyBoardChanges(results.BoardVersion, new[] {lane});
			}
			finally
			{
				_boardLock.ExitWriteLock();
			}
		}
	    protected override void UpdateStateOfExternalItem(Card card, List<string> states, BoardMapping boardMapping)
	    {
			UpdateStateOfExternalItem(card, states, boardMapping, false);
	    }
		public void UpdateTask(Card task, long cardId)
		{
			UpdateTask(task, cardId, string.Empty);
		}
        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++;
			}
		}
		public void AddTaskboardCard(Card card, long taskboardId)
		{
			AddTaskboardCard(card, taskboardId, string.Empty);
		}
        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");
		        }
	        }
        }
		public void UpdateTaskboardCard(Card card, long taskboardId)
		{
			UpdateTaskboardCard(card, taskboardId, string.Empty);
		}
	    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();
		    }

	    }