Nov 132011
 

What should you do if you see the error “TF42097: A work item could not be created due to a field error” under the Other Errors and Warnings section of a failed build? The error may seem mysterious initially, because it’s a build complaining about a work item and the connection is not obvious.

The explanation is quite straightforward: the build uses the Default Template (or a derivative) and has the Create Work Item on Failure option turned on, however the Bug work item has been modified in a way that means the build is unable to create it – which could be as simple as making a field mandatory. When I saw this error it was due to putting a work around in place to force users to choose an Area, but the build template left the Area as the default – which was prohibited.

How you fix this depends on how the Bug work item has been restricted. Work item fields that have been made mandatory should have a value specified in the build process template, and if that value should vary depending on the build definition you will need to expose it as an argument in the definition. Here is a step-by-step guide:

  1. Open the build process template you are using (if using the Default Template it is recommended that you make a copy and point your build definitions to it before modifying it).
  2. Click the Arguments tab at the bottom
  3. Create a new argument for each mandatory field in the Bug work item that should have a different value depending on the build definition. Then edit the Metadata argument to make the new argument(s) user friendly in the build definition (optional!)
  4. Find the place in the build definition where the work item is created. This is hidden very well but you can find it here in the Default Template: Sequence –> Run on Agent –> Try Compile, Test, and Associate Changesets and Work Items –> Sequence –> Compile, Test, and Associate Changesets and Work Items –> Try Compile and Test –> Compile and Test –> For Each Configuration in BuildSettings.PlatformConfigurations –> Compile and Test for Configuration –> If BuildSettings.HasProjectsToBuild –> For Each Project in BuildSettings.ProjectsToBuild –> Try to Compile the Project –> Exception –> Handle Exception –> If CreateWorkItem –> Create Work Item for non-Shelveset Builds –> Create Work Item
  5. Open the Properties for the Create Work Item activity and expand the Custom Fields property. Thankfully this is just a Dictionary of key-value pairs and quite easy to edit. Before the final closing brace just add additional pairs of work item field reference names and the value they should have. If you need to pass through the value of an argument you may find this a little tricky – all the braces and escaping in the underlying XML make it difficult to pass in arguments normally so you may need to wrap them in a String.Format(“{0}”, MyArgument) statement.

My final OpenWorkItem activity looked like:


<mtbwa:OpenWorkItem AssignedTo="[BuildDetail.RequestedFor]"
Comment="[&quot;This work item was created by TFS Build on a build failure.&quot;]"
CustomFields="[New Dictionary(Of String, String) From { {&quot;System.Reason&quot;, &quot;Build Failure&quot;},
{&quot;Microsoft.VSTS.TCM.ReproSteps&quot;, &quot;Start the build using TFS Build&quot;},
{&quot;Microsoft.VSTS.CMMI.Symptom&quot;, &quot;Build Failure&quot;}, {&quot;Priority&quot;, &quot;1&quot;},
{&quot;Severity&quot;, &quot;1 - Critical&quot;}, {&quot;Microsoft.VSTS.CMMI.FoundInEnvironment&quot;, &quot;n/a&quot;},
{&quot;System.AreaPath&quot;, String.Format(&quot;{0}&quot;, WorkItemArea) }, {&quot;MyCo.Client&quot;, &quot;n/a&quot;},
{&quot;MyCo.Project&quot;, &quot;BAU&quot;} }]"
DisplayName="Create Work Item" Title="[String.Format(&quot;Build Failure in Build: {0}&quot;, BuildDetail.BuildNumber)]"
Type="[&quot;Bug&quot;]" />
<pre>

There’s a lot of escaping to make it valid XML so it’s best to edit in the designer!

Global Workflow and Visual Studio SP1

 Comments Off on Global Workflow and Visual Studio SP1
Nov 062011
 

When Service Pack 1 for TFS 2010 came out of beta earlier this week I quickly installed it on the server so I could use the new Global Workflow feature.

Global Workflow is a great addition to TFS that allows you to set up field rules so they apply across all work items, rather than having to duplicate them in each work item type definition (WITD). I had done just that so I was keen to remove the duplication and centralize my field customisations. An added bonus in Global Workflow is that it is backwards-compatible with Visual Studio 2010 RTM (i.e. without SP1) so I didn’t have to worry about upgrading all the business users who use Team Explorer to submit and manage work items.

All was well until recently, when a (relatively) complex field rule just wouldn’t work for the business users. It worked fine for the development team and I scratched my head for a while, wondering if there was some buried permission making it only work for a specific group. It turned out that the backwards-compatibility with Visual Studio 2010 RTM was not perfect, and the developers saw the correct behaviour only because they had installed SP1.

Here’s the difference in the application of Global Workflow for Visual Studio RTM and Visual Studio SP1:

Visual Studio SP1

Global Workflow rules are applied before rules in the individual WITD. This gives a nice layering so that work item rules have an “override” effect on global rules. In other words, you could restrict a field globally using global workflow then soften or harden up the rules as required for a specific work item type.

Visual Studio RTM

TFS recognises pre-SP1 clients, mashes Global Workflow and WITD rules into one, and sends them to Visual Studio all together. You can see this using the TFS Power Tools process editor in an RTM Visual Studio – open a work item directly from the server and you will see the field rules for a work item include Global Workflow rules as if you had defined them all directly in the WITD (confusing!)

How you fix this depends on the field rule you are trying to enforce. In some cases, abandoning Global Workflow (i.e. defining the rule in each WITD) or installing SP1 for everybody may be the only option. In others, expressing the rule a different way in Global Workflow may do the trick.

My scenario was this: I wanted to prohibit users from choosing the root Area i.e. force them to choose a real Area not simply the Team Project name. This is tricky in itself as you most rules cannot be applied to the special Area field directly. However, there is a workaround where you do it by creating a new field specifically for the purpose. It gets trickier still if you have multiple Team Projects using the same process template, because the workaround requires the Team Project name to be embedded in the rule. Again, you can get around this – with a script that replaces a placeholder with the Team Project name before uploading. It’s pretty ugly to do this for every single work item (and error-prone if someone uploads a WITD containing the placeholder or downloads one without it!) so I wanted to use Global Workflow and only have to script the find-and-replace for that file. The downside to using Global Workflow is that we have some WITDs that do not include the Area field, so the rule would need bypassing for these.

Here’s how I set it up in Global Workflow:


<FIELD name="Area Selection" refname="MyCo.AreaSelection" type="String">
<WHEN field="System.AreaPath" value="TeamProjectName">
<COPY from="value" value="TeamProjectName" />
</WHEN>
<WHENNOT field="System.AreaPath" value="TeamProjectName">
<COPY from="value" value="Valid" />
</WHENNOT>
<PROHIBITEDVALUES>
<LISTITEM value="TeamProjectName" />
</PROHIBITEDVALUES>
</FIELD>

And then in each WITD where you don’t want the rule to apply:


<FIELD name="Area Selection" refname="MyCo.AreaSelection" type="String">
<COPY from="value" value="Valid" />
<FIELD>

So far so good… the global rule gets nicely negated by the copy rule by WITDs which contain it. Except it doesn’t work for Visual Studio RTM users because the rules get mashed together and applied in the wrong order (see the indispensable How Rules are Evaluated).

It took some lateral thinking, but here’s the workaround which keeps the rule centralised in Global Workflow and even removes the need for a bypass in WITDs that shouldn’t apply it – at the cost of some additional verbosity:


<!--
The ApplyTemplate script replaces "TeamProjectName" with the Team Project name.
This rule forces the user to choose an Area other than the root (cannot restrict directly).
-->
<FIELD name="Area Selection" refname="MyCo.AreaSelection" type="String">
<WHEN field="System.AreaPath" value="TeamProjectName">
<COPY from="value" value="TeamProjectName" />
</WHEN>
<WHENNOT field="System.AreaPath" value="TeamProjectName">
<COPY from="value" value="Valid" />
</WHENNOT>
<!--
The following WHEN rules are required to apply the prohibited values only to
work items that show the Area field - for Visual Studio users without SP1.
The same is achieved more simply for SP1 users by applying a COPY rule to the
MyCo.AreaSelection field of work item types that do NOT show the Area field.
(VS with SP1 runs the global rules THEN the WIT-specific rules.)
-->
<WHEN field="System.WorkItemType" value="Bug">
<PROHIBITEDVALUES>
<LISTITEM value="TeamProjectName" />
</PROHIBITEDVALUES>
</WHEN>
<WHEN field="System.WorkItemType" value="Change Request">
<PROHIBITEDVALUES>
<LISTITEM value="TeamProjectName" />
</PROHIBITEDVALUES>
</WHEN>
<WHEN field="System.WorkItemType" value="Requirement">
<PROHIBITEDVALUES>
<LISTITEM value="TeamProjectName" />
</PROHIBITEDVALUES>
</WHEN>
<WHEN field="System.WorkItemType" value="Task">
<PROHIBITEDVALUES>
<LISTITEM value="TeamProjectName" />
</PROHIBITEDVALUES>
</WHEN>
<WHEN field="System.WorkItemType" value="Test Case">
<PROHIBITEDVALUES>
<LISTITEM value="TeamProjectName" />
</PROHIBITEDVALUES>
</WHEN>
<!-- ... -->
</FIELD>

I hope that helps someone!