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 () {
var WITOM = TFS.WorkItemTracking,
WITCONTROLS = TFS.WorkItemTracking.Controls,
delegate = TFS.Core.delegate;
function WITEventHandler(container, options, workItemType)
{
this.baseConstructor.call(this, container, options, workItemType);
}
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:
TFS.module("FFFilm.WITEventHandler",
[
"TFS.WorkItemTracking.Controls",
"TFS.WorkItemTracking",
"TFS.Core"
],
function () {
var WITOM = TFS.WorkItemTracking,
WITCONTROLS = TFS.WorkItemTracking.Controls,
delegate = TFS.Core.delegate;
function WITEventHandler(container, options, workItemType) {
this.baseConstructor.call(this, container, options, workItemType);
}
WITEventHandler.inherit(WITCONTROLS.WorkItemControl, {
_control: null,
_status: null,
_init: function () {
this._base();
var oldStateValue;
var stateCtrl;
var wiClosed;
},
invalidate: function (flushing) {
},
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") {
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 != wiClosed) {
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;
}
}
});
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