Friday, May 10, 2013

Restoring state of a work item to it's last state when migrating or copying it to another project programmically

When you migrating a work item from a source project to a target project, you would want that work item to retain it's last state and reason. But you can't directly assign the last state to the migrated work item unless that state can be reached from the begin state by one transition. Because when migrating a work item what always happens is creating a new work item and assign field values of the source work item to new one. Therefore target work item's state will be the first state of it's work flow (You can see this by checking from the workflow of that work item type). So if you want to assign the last state, you have to traverse the work item through it's work flow to it's final state. As work item can perform one transition for once, you will need to save work item between transitions.

The other problem is how you can find a path between from first state to last state. one way to do this is querying work flow and trying to find out next states reachable from current states and move along the way. But this is tricky as you might find yourself in a closed corner and have to backtrack to find another path. But there is an easy way to get the required state transitions, as source work item is available for us. We can get  all the state transitions of a work item by work item history. therefore we get state transitions from source work item and replicate them in the target work item.


Code:

public void writeWorkItems(WorkItemCollection workItemCollection)
        {
        //creating new workitems for source work items
foreach (WorkItem workItem in workItemCollection)
            {

  WorkItem newWorkItem = null;
newWorkItem = new WorkItem(workItemTypes[workItem.Type.Name]);
------
-----
updateToLatestStatus(workItem, newWorkItem);
newWorkItem.Save();
   }
}

public void updateToLatestStatus(WorkItem oldWorkItem, WorkItem newWorkItem)

        {
            Queue<string> result = new Queue<string>();
            string previousState = null;
            string originalState = (string)newWorkItem.Fields["State"].Value;
            string sourceState = (string)oldWorkItem.Fields["State"].Value;
            string sourceFinalReason = (string)oldWorkItem.Fields["Reason"].Value;

            //try to change the status directly
            newWorkItem.Open();
            newWorkItem.Fields["State"].Value = oldWorkItem.Fields["State"].Value;
            //System.Diagnostics.Debug.WriteLine(newWorkItem.Type.Name + "      " + newWorkItem.Fields["State"].Value);

            //if status can't be changed directly... 
            if (newWorkItem.Fields["State"].Status != FieldStatus.Valid)
            {
                //get the state transition history of the source work item.
                foreach (Revision revision in oldWorkItem.Revisions)
                {
                    // Get Status          
                    if (!revision.Fields["State"].Value.Equals(previousState))
                    {
                        previousState = revision.Fields["State"].Value.ToString();
                        result.Enqueue(previousState);
                    }

                }

                int i = 1;
                previousState = originalState;
                //traverse new work item through old work items's transition states
                foreach (String currentStatus in result)
                {
                    bool success = false;
                    if (i != result.Count)
                    {
                        success = ChangeWorkItemStatus(newWorkItem, previousState, currentStatus);
                        previousState = currentStatus;
                    }
                    else
                    {
                        success = ChangeWorkItemStatus(newWorkItem, previousState, currentStatus, sourceFinalReason);
                    }
                    i++;
                    // If we could not do the incremental state change then we are done.  We will have to go back to the orginal...
                    if (!success)
                        break;
                }
            }
            else
            {
                // Just save it off if we can.
                bool success = ChangeWorkItemStatus(newWorkItem, originalState, sourceState);
            }
        }

        private bool ChangeWorkItemStatus(WorkItem workItem, string orginalSourceState, string destState)
        {
            //Try to save the new state.  If that fails then we also go back to the orginal state.
            try
            {
                workItem.Open();
                workItem.Fields["State"].Value = destState;
                workItem.Save();
                return true;
            }
            catch (Exception)
            {
                logger.Info(String.Format("Failed to save state for workItem : {0}  type:{1} state from {2} to {3}", workItem.Id, workItem.Type.Name, orginalSourceState, destState));
                logger.Info(String.Format("rolling workItem status to original state {0}", orginalSourceState));
                //Revert back to the original value.
                workItem.Fields["State"].Value = orginalSourceState;
                return false;
            }
        }

        //save final state transition and set final reason.
        private bool ChangeWorkItemStatus(WorkItem workItem, string orginalSourceState, string destState, string reason)
        {
            //Try to save the new state.  If that fails then we also go back to the orginal state.
            try
            {
                workItem.Open();
                workItem.Fields["State"].Value = destState;
                workItem.Fields["Reason"].Value = reason;

                ArrayList list = workItem.Validate();
                workItem.Save();

                return true;
            }
            catch (Exception)
            {
                logger.Info(String.Format("Failed to save state for workItem: {0}  type:{1} state from {2} to {3}", workItem.Id.ToString(), workItem.Type.Name, orginalSourceState, destState));
                logger.Info(String.Format("rolling workItem status to original state {0}", orginalSourceState));
                //Revert back to the original value.
                workItem.Fields["State"].Value = orginalSourceState;
                return false;
            }
        }




Tuesday, May 7, 2013

Migrate all your project work items to another project : Total TFS Migration Tool

As I mentioned in my first post of  Creating a Test Plans and Cases Migration Tool For TFSWe had to move some team projects from TFS 2010 Server to a newly created TFS 2012 Server. Usually You can restore all your projects by using a tfs backup. But there can be many reasons that will cause you to need a migration tool. In our case our database and backup are both have corrupted. Therefore we were unable to do a restore from backup.

Migration involves moving all source files,work items, attachments, links and team queries. Though moving source files is an easy task, moving work items is not an easy feat. This is due to many reasons. Such as TFS 2010 use the Scrum 1.0 template and TFS 2012  use Scrum 2.0 template(Update 2 use 2.2 template). Migration is done through the APIs of TFS by using external tools, and is a lossy data transfer. There were some migration tools but  it all have certain limitations; such as not supporting test case migration, permission, etc. Therefore we decided to built a custom tool that match our need.

After we finished building a custom tool to match our needs, we thought of developing a generic tool that might help others who have the same need; as there isn't a generic tool to do this. Building a total generic tool for TFS Migration is almost impossible as every company that use TFS as a source control software and as a team management software, have customized it to suit their needs and development methodologies. But we tried to make it as generic as we can. But as the source code is available, anyone can customize this tool to match their requirements. The tool is available at  CodePlex -  Total TFS Migration Tool.



Scope of the tool:
At this point in time, the TFS Migration Tool is scoped to only support the following TFS features:
  • Work Item Migration - This includes the migration of all work items, fields, in-use areas, iterations, links, attachments, shared queries.
  • Test Plan Migration – This includes migration of all test plans and test suits.


Limitations:
  • No Version Control – Project source code will not be transferred.
  • Work item history will not be transferred.

Features:
  • Copy custom fields of relevant work item types in source project to target project. 
  • Copy workflows from source TFS project to target TFS project.
  • Map the mismatched source fields with target fields.
  • Logging mechanism to track entire migration process

How to use the tool is described in full detail in the documentation. Feedback from end users will also be critical to make this more generic, so please help us by sharing your experiences and feedback.

Note:
Tool can be used to copy custom fields and work flows, but if you prefer you can manually copy them using Process Editor and ignore those tabs in the tool as those features are not required by the tool.

Wednesday, May 1, 2013

How To Create a Test Plans and Cases Migration Tool For TFS : Part 4

All my previous posts (part 1, Part 2, and Part 3) have covered the knowledge needed you to build a test plan migration tool for TFS. I put all those together and create a simple WPF project that you can find it here.
Tool has two operational modes.

1) Migration
2) Restore

In Migration mode it will copy test cases from source project to target project as well as test plans and suits.  tool will copy all test plans, suits and test cases from the source project and create Test plans, suits and  cases similar to source project. Not only test cases, it will also copy all test steps into test cases and duplicate shared steps that's linked to test cases. You can choose whether or not to duplicate shared steps. but if you choose not to include  shared test steps, software will not add any shared steps. 

But In Restore mode it will only copy test plans and suits. but it will connect appropriate test cases from querying existing work item collection(Restore mode is when you have all test cases in TFS, but lost test plans and have the option of restore those plans from an older test plan version).  This can be happen as deleting test plans only delete test plans and test suits and test cases will remain as they are work items. Therefore in this mode you won't need restoring test cases, shared steps, attachments.



Migration Mode
Connect to source and target project and select options you need.





























Halfway through the migration



























Migration completed



























check test plan,suit and case hierarchy























Restore Mode




























Limitations

One problem I was unable to solve is including links of Test cases to other work items while copying test cases when operating in Migration mode.
Including attachments part is still in progress. Also I hope to include copying iterations and areas in future. For now all the newly created test cases will be in 'New' status, not in their last recorded states. 

You can find the tool here,
https://tfstestpscexporter.codeplex.com