Search code examples
tfs-sdk

Edit TFS work-item field when marked as read-only using TFS API


I am trying to update a field of a work-item using the managed TFS API.

However, certain states mark the field as read-only (for example, the Done state) and at that point I'm not able to modify the field, even when using the TFS API. I noticed that the IsEditable field returns False, but I cannot set it.

var effort = Convert.ToSingle(workItem.Fields["Effort"].Value);
workItem.Fields["Effort"].Value = effort / 2.0f;

As my code works for every state in which the field is editable I'm fairly confident it works. When checking the work-item using the Team Explorer UI I can see my changes are applied and tracked in the history of the work-item. This is also reflected in all of the reports that are generated.

How can I modify the field after it has been marked as read-only? Is it even possible? (I could switch the state back so the field is editable, but that's something I'd like to avoid as I have no idea what kind of impact this could have on my velocity and burndown charts.)


Solution

  • As it turns out, this cannot be done directly when using the managed API. When using the web-service API, there is a BypassRules property which can be set.

    The web-service API requires an XML package, so the difficulty lies in creating it.

    First, connect to the server and collection like you'd normally would.

    var tpc = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(
       new Uri(SERVER_URI), new UICredentialsProvider());
    tpc.EnsureAuthenticated();
    
    var store = tpc.GetService<WorkItemStore>();
    var server = tpc.GetService<WorkItemServer>();
    

    After I made the connection, I assembled a list of work-item ID's I wanted to modify. In my case, I needed to divide all work-item Efforts by 2.

    The XML package syntax is fairly straight forward. One thing to note is that I explicitly needed to convert my effort value (a float) to a string, even though the internal type is double. (I might have done something wrong here, but the end result was what I wanted.)

    private static StringBuilder GetXmlPackage(WorkItemStore store, IEnumerable<int> workItemIds)
    {
        var sb = new StringBuilder();
        sb.Append("<Package>");
    
        foreach (var id in workItemIds)
        {
            var item = store.GetWorkItem(id);
            var effort = Convert.ToSingle(item.Fields["Effort"].Value) / 2.0f;
    
            sb.AppendFormat("<UpdateWorkItem ObjectType='WorkItem' BypassRules='1' WorkItemID='{0}' Revision='{1}'>", item.Id, item.Rev.ToString());
            sb.Append("<Columns>");
            sb.AppendFormat("<Column Column='Microsoft.VSTS.Scheduling.Effort' Type='Double'><Value>{0}</Value></Column>", XmlConvert.ToString(effort));
            sb.Append("</Columns>");
            sb.Append("<InsertText FieldName='System.History' FieldDisplayName='History'>Updated effort.</InsertText>");
            sb.Append("</UpdateWorkItem>");
        }
    
        sb.Append("</Package>");
        return sb;
    }
    

    The last thing to do is call the web-server and do a bulk update to modify all work-items.

    private static void UpdateWorkItems(WorkItemStore store, WorkItemServer server, IEnumerable<int> workItemIds)
    {
        var sb = GetXmlPackage(store, workItemIds);
    
        var mthe = new MetadataTableHaveEntry[0];
        string dbStamp;
        IMetadataRowSets rowSets;
        XmlElement outElement;
    
        var doc = new XmlDocument();
        doc.LoadXml(sb.ToString());
    
        server.BulkUpdate(
            WorkItemServer.NewRequestId(),
            doc.DocumentElement,
            out outElement,
            mthe,
            out dbStamp,
            out rowSets);
    }