1
votes

I am developing a windows app that performs some common TFS tasks using the 2010 Beta 2 API (like creating new team projects, new work items, selective build, etc. ).

In the process of editing existing work items, I should be able to automatically set the 'Reason' field's values according to state change of the WI (mimic-ing Visual Studio). (eg)- When I edit a bug, when state changes from Active to Resolved, the default Reason is 'Fixed' and similarly the default Reason='Deferred' when state goes from Active to Closed. (As defined in the work item type definition xml file. ) This transition is easy to capture and implement inside a simple event handler on the form, since the initial state will be Active when the Bug is edited for the first time.

I want to know how to implement the remaining transitions like Resolved to Closed (Reason=Fixed), Resolved to Active (Reason=Test failed/Not fixed) or Closed to Active (Reason=Reactivated/Regression).

I know there is a method called WorkItem.GetNextState(current_state,action), but this doesn't help as it requires a specific action.

What I have done so far is shown below:

void cmbBugState_SelectedIndexChanged(object sender, EventArgs e)
    {
        //private enum bugWorkFlows{"Fixed","Deferred","Duplicate","As Designed","Cannot Reproduce","Obsolete","Test Failed","Not Fixed","Reactivated","Regression"}
        string[] activeToResolvedReasons = { "Fixed", "Deferred", "Duplicate", "As Designed", "Cannot Reproduce", "Obsolete" };
        string[] resolvedToActiveReasons = { "Test Failed", "Not fixed" };
        string[] resolvedToClosedReasons = activeToResolvedReasons;
        string[] closedToActiveReasons = { "Reactivated", "Regression" };
        string[] activeToClosedReasons = activeToResolvedReasons;

        cmbBugReason.Items.AddRange(activeToResolvedReasons);
        // Set the default reason according to change of state of the work item.
        if (cmbBugState.SelectedItem.ToString() == "Resolved")
        {
            cmbBugReason.Enabled = true;
            cmbBugReason.SelectedItem = activeToResolvedReasons[0];
        }
        if (cmbBugState.SelectedItem.ToString() == "Closed")
        {
            cmbBugReason.Enabled = true;
            cmbBugReason.SelectedItem = activeToResolvedReasons[1];
        }
    }

Can anyone show how to handle these events on the form?

Thanks, Tara.

1

1 Answers

1
votes

I tried GetNextState. It was never reliable enough for what I needed.

So I "rolled my own" state transition code that has worked very well for me when I am moving from State "A" to State "B". It is a bit long, but it should have what you are looking for in it.

As a side note: Because this does not use the GetNextState method it has to get the next state somehow. The way it does this is by downloading the XML of the work item type in question. It parses that out and uses that to make a Transition list (_allTransistions).

The permissions levels in TFS 2010 needed to do this are: Team Foundation Administrators or Project Administrators. (As a side note, in TFS 2008 and 2005 all valid users could do this.)

The full code that uses this can be found in the WorkItemHelpers.cs file in the TFS Aggregator project on codeplex.

public static void TransitionToState(this WorkItem workItem, string state, string commentPrefix)
{
    // Set the sourceWorkItem's state so that it is clear that it has been moved.
    string originalState = (string)workItem.Fields["State"].Value;

    // Try to set the state of the source work item to the "Deleted/Moved" state (whatever is defined in the file).

    // We need an open work item to set the state
    workItem.TryOpen();

    // See if we can go directly to the planned state.
    workItem.Fields["State"].Value = state;


    if (workItem.Fields["State"].Status != FieldStatus.Valid)
    {
        // Revert back to the orginal value and start searching for a way to our "MovedState"
        workItem.Fields["State"].Value = workItem.Fields["State"].OriginalValue;

        // If we can't then try to go from the current state to another state.  Saving each time till we get to where we are going.
        foreach (string curState in workItem.Type.FindNextState((string)workItem.Fields["State"].Value, state))
        {
            string comment;
            if (curState == state)
                comment = commentPrefix + Environment.NewLine + "  State changed to " + state;
            else
                comment = commentPrefix + Environment.NewLine + "  State changed to " + curState + " as part of move toward a state of " + state;

            bool success = ChangeWorkItemState(workItem, originalState, curState, comment);
            // 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.
        string comment = commentPrefix + "\n   State changed to " + state;
        ChangeWorkItemState(workItem, originalState, state, comment);

    }
}
private static bool ChangeWorkItemState(this WorkItem workItem, string orginalSourceState, string destState, String comment)
{
    // Try to save the new state.  If that fails then we also go back to the orginal state.
    try
    {
        workItem.TryOpen();
        workItem.Fields["State"].Value = destState;
        workItem.History = comment;
        workItem.Save();
        return true;
    }
    catch (Exception)
    {
        // Revert back to the original value.
        workItem.Fields["State"].Value = orginalSourceState;
        return false;
    }
}

/// <summary>
/// Used to find the next state on our way to a destination state.
/// (Meaning if we are going from a "Not-Started" to a "Done" state, 
/// we usually have to hit a "in progress" state first.
/// </summary>
/// <param name="wiType"></param>
/// <param name="fromState"></param>
/// <param name="toState"></param>
/// <returns></returns>
public static IEnumerable<string> FindNextState(this WorkItemType wiType, string fromState, string toState)
{
    var map = new Dictionary<string, string>();
    var edges = wiType.GetTransitions().ToDictionary(i => i.From, i => i.To);
    var q = new Queue<string>();
    map.Add(fromState, null);
    q.Enqueue(fromState);
    while (q.Count > 0)
    {
        var current = q.Dequeue();
        foreach (var s in edges[current])
        {
            if (!map.ContainsKey(s))
            {
                map.Add(s, current);
                if (s == toState)
                {
                    var result = new Stack<string>();
                    var thisNode = s;
                    do
                    {
                        result.Push(thisNode);
                        thisNode = map[thisNode];
                    } while (thisNode != fromState);
                    while (result.Count > 0)
                        yield return result.Pop();
                    yield break;
                }
                q.Enqueue(s);
            }
        }
    }
    // no path exists
}

private static readonly Dictionary<WorkItemType, List<Transition>> _allTransistions = new Dictionary<WorkItemType, List<Transition>>();

/// <summary>
/// Deprecated
/// Get the transitions for this <see cref="WorkItemType"/>
/// </summary>
/// <param name="workItemType"></param>
/// <returns></returns>
public static List<Transition> GetTransitions(this WorkItemType workItemType)
{
    List<Transition> currentTransistions;

    // See if this WorkItemType has already had it's transistions figured out.
    _allTransistions.TryGetValue(workItemType, out currentTransistions);
    if (currentTransistions != null)
        return currentTransistions;

    // Get this worktype type as xml
    XmlDocument workItemTypeXml = workItemType.Export(false);

    // Create a dictionary to allow us to look up the "to" state using a "from" state.
    var newTransistions = new List<Transition>();

    // get the transistions node.
    XmlNodeList transitionsList = workItemTypeXml.GetElementsByTagName("TRANSITIONS");

    // As there is only one transistions item we can just get the first
    XmlNode transitions = transitionsList[0];

    // Iterate all the transitions
    foreach (XmlNode transitionXML in transitions)
    {
        // See if we have this from state already.
        string fromState = transitionXML.Attributes["from"].Value;
        Transition transition = newTransistions.Find(trans => trans.From == fromState);
        if (transition != null)
        {
            transition.To.Add(transitionXML.Attributes["to"].Value);
        }
        // If we could not find this state already then add it.
        else
        {
            // save off the transistion (from first so we can look up state progression.
            newTransistions.Add(new Transition
            {
                From = transitionXML.Attributes["from"].Value,
                To = new List<string> { transitionXML.Attributes["to"].Value }
            });
        }
    }

    // Save off this transition so we don't do it again if it is needed.
    _allTransistions.Add(workItemType, newTransistions);

    return newTransistions;
}