Close a Work Item Only if Child Work Items are Closed - WebAccess





5.00/5 (2 votes)
Custom controls over WebAccess of TFS
ABSTRACT
In the developing world, with the most flexible applications available, we have been provided an opportunity to extend the functionality of the software. Similar software in the market does not match the required functionality, but one would be more inclined to a particular application based on the features it is offering, like free, open source, extensibility, expertise, etc. The application one has chosen may not have a specific functionality another application is providing or need a new feature as per the business requirement. To incorporate this functionality, we may not be able to contact the vendor due to various reasons like, the cost of support, time, frequent changes, etc., To overcome this situation, few vendors are providing APIs which enable users/developers to extend the application as per our needs. Some may be simple configuration changes while others would be extending the functionality using the APIs provided. This enables us to incorporate the functionality we need, and the control is in our hands.
Introduction
Team Foundation Server is an ALM tool. It can be extended as per the business requirement. Controls can be created so as to populate them on the work item form. We can add additional business functionality by creating the controls. The API provided by TFS makes this possible. Closing a work item only if child work items are closed is not as part of TFS standard features. We need to extend the functionality to achieve this, by using the API provided by TFS. TFS can be used with the help of various interfaces. Visual studio, web are the most prominent. In this paper, I would explain how to achieve the above described functionality over web. Look for my other papers here for Visual Studio.
Background
TFS being a configuration management tool, which stores versions of code, later on moved to be an Application Life cycle management(ALM) tool. Since providing the additional feature, it was not as efficient as the other tools available in the market, could be due to the process templates we use. Because of this, we need to develop our custom functionality into TFS, which is possible by the provided API from Microsoft for TFS. To provide flexibility to the team members, the ALM can be operations through various platforms, out of which web is prominent, which can be accessed from any device.
Need for Restricting to Close a Work Item if Children are Open
TFS being an ALM tool, user stories, tasks, issues, bugs, etc., can be logged into it. We can apply proven agile practices to manage our applications lifecycle. Though the functionality described in this paper is not available as part of standard feature, we intend to develop one. Creating one such tool enables us not only follow the proven agile practice, but also ease for the developers, managers, etc., to keep track of work.
Definitions
WIT |
Work Item Type |
ALM |
Application Lifecycle Management |
.WICC |
Work Item Custom Control |
Developing the Control for Web Interface
NOTE: We will be developing a JQuery plugin which needs to be installed on to the TFS web server.
Create a .js file and create a TFS module to register as FF.WITEventHandler
. Declare dependencies on TFS.WorkItemTracking.Controls
, TFS.WorkItemTracking
and TFS.Core
modules.
TFS.module("FujiFilm.WITEventHandler",
[
"TFS.WorkItemTracking.Controls",
"TFS.WorkItemTracking",
"TFS.Core"
]
Create a
constructor and inherit it from TFS.WorkItemTracking.Controls.WorkItemControl
.
function () {
// module content
var WITOM = TFS.WorkItemTracking,
WITCONTROLS = TFS.WorkItemTracking.Controls,
delegate = TFS.Core.delegate;
// Constructor for WITEventHandler
function WITEventHandler(container, options, workItemType)
{
this.baseConstructor.call(this, container, options, workItemType);
}
// WITEventHandler inherits from WorkItemControl
WITEventHandler.inherit(WITCONTROLS.WorkItemControl, {
_control: null,
_status: null,
WITCONTROLS.registerWorkItemControl("WITEventHandler", WITEventHandler);
}
Initialize the control UI. There is where all the global variables go.
_init:function () {
this._base();
var oldStateValue;
},
Update control data when work item is bound to a specific item.
invalidate: function (flushing) {
},
Clear the
control data. Framework calls this method when the control needs to reset its
state to "blank
", such as when work item form is unbound from a
specific work item.
clear: function() {
this._workItem= null;
},
Bind a function to the work item where we handle all the logic.
bind:function (workItem) {
}
Within the above function, create a function delegate and attach it to the workitem changed event in bind above.
bind:function (workItem) {
this._workItemChangeDelegate = function (sender, args) {
}
workItem.attachWorkItemChanged(this._workItemChangeDelegate);
}
So when a workitem changes, this._workItemChangeDelegate
gets fired. This method contains the logic we need to implement for our
requirement.
Logic
Logic is simple,
- Identify if the work item is changed
- If changed then check if it is the state control
- If its state control and its changed to Close
- Check if the child work items are open
- If child work items are open then
- Raise an alert to the user
- And revert back the change
Identify the Control Clicked
If you closely observe, the controls generated on the TFS work item web form are dynamic controls. We need a mechanism to identify the control clicked.
So create an event for every dropdown. (In our case, we are working only with state, so I selected dropdown.)
We are writing an event to the dropdown button highlighted above.
$('.drop').bind('click', function () {
if (this.id == "")
stateCtrl = $("#" + this.parentElement.id + "_txt")[0];
else
stateCtrl = $("#" + this.id + "_txt")[0];
});
When the button is clicked, create a variable to load the control’s parent as that is the actual dropdown control.
Store the previous value in a variable called
oldStateValue = workItem.fieldData[2];
Now identify which work item you want to check, Epic, User Story, Task, etc.
if (workItem.workItemType.name == "Epic")
wiClosed = "Resolved";
if (workItem.workItemType.name == "Task")
wiClosed = "Closed";
if (workItem.workItemType.name == "User Story")
wiClosed = "Closed";
Now create a work item changed delegate function.
Check if the change is a ‘field-change
’.
if (args.change === "field-change")
There will be multiple auto field changes once a field is changed in a work item, like changed date, changed by, etc. For each changed value, we are interested in change of state field which is changed to close.
for (var i in args.changedFields) {
if (args.changedFields[i].fieldDefinition.name === "State" && stateCtrl.value === wiClosed) {
Get the links for the work item. Links could be children, parent, etc., we need to check only for children.
var links = parentWorkItem.getLinks();
for (var i in links) {
var child = null;
if (links[i].baseLinkType === "WorkItemLink" && links[i].getLinkTypeEnd().name == "Child") {
If children are available, then check the state of the child. If the child state is not closed, then show an alert to the user and reset the state value to the previous value.
state = child.fieldMap.STATE.getValue();
if (state != wiClosed) {
alert("You must close all the open child work items to close this work item.");
stateCtrl.value = oldStateValue;
Here we are displaying only a basic alert to the user, but to the details of the open children. So break out of the loop if atleast one open child is found.
The final code looks as below:
// Register this module as "FFFilm.WITEventHandler" and declare
// dependencies on TFS.WorkItemTracking.Controls, TFS.WorkItemTracking and TFS.Core modules
TFS.module("FFFilm.WITEventHandler",
[
"TFS.WorkItemTracking.Controls",
"TFS.WorkItemTracking",
"TFS.Core"
],
function () {
// module content
var WITOM = TFS.WorkItemTracking,
WITCONTROLS = TFS.WorkItemTracking.Controls,
delegate = TFS.Core.delegate;
// Constructor for WITEventHandler
function WITEventHandler(container, options, workItemType) {
this.baseConstructor.call(this, container, options, workItemType);
}
// WITEventHandler inherits from WorkItemControl
WITEventHandler.inherit(WITCONTROLS.WorkItemControl, {
_control: null,
_status: null,
// Initialize the control UI without data (in "blank" state).
// Framework calls this method when the control needs to render its initial UI
// Notes:
// - The work item data is NOT available at this point
// - Keep in mind that work item form is reused for multiple work items
// by binding/unbinding the form to work item data
_init: function () {
this._base();
var oldStateValue;
var stateCtrl;
var wiClosed;
},
// Update the control data
// Framework calls this method when the control needs to update itself, such as when:
// - work item form is bound to a specific work item
// - underlying field value has changed due to rules or another control logic
invalidate: function (flushing) {
},
// Clear the control data
// Framework calls this method when the control needs to reset its state to "blank", such as when:
// - work item form is unbound from a specific work item
clear: function () {
this._workItem = null;
},
bind: function (workItem) {
this._base(workItem);
$('.drop').bind('click', function () {
if (this.id == "")
stateCtrl = $("#" + this.parentElement.id + "_txt")[0];
else
stateCtrl = $("#" + this.id + "_txt")[0];
});
oldStateValue = workItem.fieldData[2];
if (workItem.workItemType.name == "Epic")
wiClosed = "Resolved";
if (workItem.workItemType.name == "Task")
wiClosed = "Closed";
if (workItem.workItemType.name == "User Story")
wiClosed = "Closed";
this._workItemChangeDelegate = function (sender, args) {
if (args.change === "field-change") {
// find a label field, with class workitemcontrol-label and
// title contans helptext of args.changedFields[i].fieldDefinition
// get that for value which is the state control
for (var i in args.changedFields) {
if (typeof stateCtrl != 'undefined') {
if (args.changedFields[i].fieldDefinition.name === "State" && stateCtrl.value === wiClosed) {
var parentWorkItem = workItem;
var links = parentWorkItem.getLinks();
for (var i in links) {
var child = null;
var state = "";
if (links[i].baseLinkType === "WorkItemLink" && links[i].getLinkTypeEnd().name == "Child") {
parentWorkItem.store.beginGetWorkItem(links[i].getTargetId(), function (child, state) {
state = child.fieldMap.STATE.getValue();
// if state is not closed, then return true
if (state != wiClosed) {
// if the previous state is already the same, then do not display any thing to the user.
if (stateCtrl.value != oldStateValue) {
alert("You must close all the open child work items to close this work item.");
stateCtrl.value = oldStateValue;
}
}
});
break;
}
}
}
}
}
}
}
workItem.attachWorkItemChanged(this._workItemChangeDelegate);
},
unbind: function (workItem) {
if (this._workItemChangeDelegate) {
this._workItem.detachWorkItemChanged(this._workItemChangeDelegate);
delete this._workItemChangeDelegate;
}
}
});
// Register this module as a work item custom control called "WITEventHandler"
WITCONTROLS.registerWorkItemControl("WITEventHandler", WITEventHandler);
return {
WITEventHandler: WITEventHandler
};
});
Creating the JQuery class is not sufficient to use the functionality. We need a manifest file stating so.
Create a manifest.xml and copy the below content into it:
<WebAccess version="11.0">
<plugin name="Work Item Template Event Handler" vendor="FFFilm" moreinfo="http://www.FFmed.com" version="1.0.0" >
<modules>
<module namespace="FFFilm.WITEventHandler" kind="TFS.WorkItem.CustomControl"/>
</modules>
</plugin>
</WebAccess>
Packaging
We need only server side deployment in this case. The user needs admin permissions on TFS to get this done.
Before we deploy, we need to package the files we created.
Name the JS file as FFFilm.WITEventHandler.min.js. You can minify the file if required.
Make a copy of the file are rename is as FujiFilm.WITEventHandler.debug.js
Zip the above three files including Manifest.xml. Do not place them in a folder before zipping. The name of the zip file doesn’t matter.
This creates our package.
Deployment
Open TFS WebAccess.
Go to Control Panel. Click on Extensions tab. You should see all the previous plugins installed here.
Click in the install button to install the plugin you created now. If this button is not visible, then you do not have the required permissions.
Choose file and point to the zip file we created. And click ok to install the plugin.
By default, the installed plugin will be disabled. Click on enable to enable it.
Now navigate to the work item and perform your testing.
Modifying a Work Item Type
Installing the files will not be sufficient, as we are trying to modify the work item, we need to tell work item to use this new control we developed. For this, we need to modify the Work Item template.
To do this, we have multiple third party tools, of which TFS Power tools is prominent.
From Visual Studio, navigate to tools-> Process Editor -> Work Item Types-> Open WIT from server.
Select the project you prefer and select Task from the expansion.
Click New to add a new field.
Fill in the following details:
Go to the layout section and add the control.
Make sure you don’t add any Label to the control, as we do not want the control to be displayed on the Work Item Form in Visual Studio.
Though the control we built doesn’t have any interface, adding the Label will display the Label text and also a default text box.
Once complete, click on save, which saves the Work Item to the server.
Getting both Visual Studio and WebAccess to Work
To get both Visual Studio and Web access to work, you need not make any more modifications to the work item type. The modifications made above will suffice the Visual Studio also. Make sure the control name matches.
Debugging
Debugging is simple in this case. In Chrome, press F12 key to open the developer tools window.
Select the source tab, and expand the folders on the left. Go to the folder as specified below where you will find the .js file you created. Click on the line number to set a break point, and refresh the browser to hit the break point.
Extending the Control
The code and
description in this paper is related to a Task work item and for change of
State control. This can be extended to other controls with minor modifications.
This can also be extended to other work item types by just modifying the work
item types. It’s like, install once and use if for many other work item types.
Taking this code as a baseline, we can develop multiple controls which can
satisfy Agile practices.
Challenges Faced
- Capturing on change event of a control
Written a handler to capture any event on the form. - Eliminating the multiple alert to display the child
details
Displayed only one generic message. -
The handler created
should be detached and removed from the work item. Else on every change to the
next work item, the handler stays and repeated multiple alert boxes comes up annoying the user.
Navigated to the TFS.Controls.js to identify exactly how to detach the handler. -
Frequently copying the assembly files to the
specified location.
Created a batch file which copies .dll, .pdb and .wicc files
References
Work Item Custom Control Development in TF Web Access 2012 – Deployment |
|
TFS 2013 PowerTools |
http://visualstudiogallery.msdn.microsoft.com/f017b10c-02b4-4d6d-9845-58a06545627f |