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.)
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);
}