The Work Item Time Tracking API


Recently I was contacted by a colleague about how to work with work item time tracking, available in the Formal Project Management Process. This is an area of the work item API I had not touched so far. So I decided to have a look at it and share the result.

License

The post contains published code, so our lawyers reminded me to state that the code in this post is derived from examples from Jazz.net as well as the RTC SDK. The usage of code from that example source code is governed by this license. Therefore this code is governed by this license. I found a section relevant to source code at the and of the license.

Please also remember, as stated in the disclaimer, that this code comes with the usual lack of promise or guarantee.

On the other hand, you have the code and are able to add your own code to it. It would be nice to know what you did and how, if you do so.

Just Starting With Extending RTC?

If you just get started with extending Rational Team Concert, or create API based automation, start with the post Learning To Fly: Getting Started with the RTC Java API’s and follow the linked resources.

You should be able to use the code attached to this post in the development environment you set up in the Rational Team Concert Extensions Workshop and get your own extensions or automation working there as well.

Download

The code is available for download here.

Work Item Time Tracking

Time Tracking can be enabled in projects based on the Formal Project Management Process. If enabled the user can no longer edit the time spent attribute of work items. Instead the user adds time sheet entries with hours worked on specific time codes on a special tab of the work item editor. The information will be summed up and the total work time will be automatically set in the work item’s time spent attribute.

The image below shows the time tracking data.

Time TrackingTab

The time spent attribute is read-only and calculated from the data above.

Time TrackingTime Spent

In certain circumstances it would be desirable to be able to access the data using the API. The question is, how is the data stored? How can it be read and, most important, how can it be updated using the API?

The Time Tracking API

There is a short explanation in the wiki about how the time tracking API works in general. However, this leaves out some very important details.

Time sheets are stored in RTC as special objects of type ITimeSheetEntry. These objects are associated to the work item they belong to using a special link type using the end point WorkItemEndPoints.WORK_TIME.

It is easy enough to follow the Wiki Entry and create a new time sheet entry.

However, I found out the hard way that it is not as easy to update an ITimeSheetEntry.

The reason is, that the work item API does not detect an update to a time sheet entry alone. So just updating the entry and then saving the work item will not work. The work item API will detect that no change has been done to the work item itself and not perform a save operation. Since the work item save also triggers the save of the dependent work item data, the save for the time sheet entry is also not performed. Adding a new relationship will also not work as you will end up with multiple relationships to the same item.

The only way to update the time sheet data is to force an update to the work item by setting the duration value. This way the API detects that it has to save the work item as well as the time sheet entries as dependent items.

The code I finally ended up with looks like below.

public void updateOrCreateTimeSheetEntry(WorkItemWorkingCopy workingCopy,
		ITimeCode timeCode, Timestamp startDateTimeStamp,
		Duration workDuration, Identifier workType,
		IProgressMonitor monitor) throws TeamRepositoryException {

	// set the active work item from the working copy
	setWorkItem(workingCopy.getWorkItem());

	// Find a matching time sheet if it exists.
	ITimeSheetEntry timeSheet = findTimeSheetEntry(timeCode,
			startDateTimeStamp, monitor);
	if (timeSheet == null) {
		// There is no time sheet for this entry
		// Create a new one and create the link
		timeSheet = createTimeSheet();
		workingCopy.getReferences()
				.add(WorkItemEndPoints.WORK_TIME,
						IReferenceFactory.INSTANCE
								.createReferenceToItem(timeSheet));
		// Add the entry to the map to hold the data
		addEntry(timeSheet, monitor);
	} else {
		// There is a time sheet, we need to update it
		// Get the workingCopy of the time sheet
		timeSheet = (ITimeSheetEntry) timeSheet.getWorkingCopy();
		// remove the time spent from current time
		setTotalDuration(new Duration(getTotalDuration().longValue()
				- timeSheet.getTimeSpent().longValue()));
	}

	// Set the new data
	timeSheet.setStartDate(startDateTimeStamp);
	timeSheet.setTimeCodeId(timeCode.getTimeCodeId());
	// TODO: If I leave this out it fails....
	timeSheet.setTimeCode(timeCode.getTimeCodeLabel());
	timeSheet.setTimeSpent(workDuration);
	timeSheet.setWorkType(workType);
	// add the new time back
	setTotalDuration(getTotalDuration().add(workDuration));
	// Update the value
	// Note: it is important to set the duration value, of the work item
	// otherwise the work item is not marked as dirty and in need to update
	// in the repository and the save process will not save the time sheet
	getWorkItem().setDuration(getTotalDuration().longValue());
	workingCopy.getDependentItems().add(timeSheet);
}

How does the code above work?

First the code tries to find a time sheet entry for the time code and the date that is given.

If it can not find a time sheet entry, a new entry is created and the required reference is also created and added to the work item references. This sets the work item to changed already.

If a time sheet entry can be found, the code gets a working copy that can be modified. The total duration of all the time spent on time sheets is reduced by the amount of time spent on that time sheet (as if the time sheet duration was set to zero).

The data on the tine sheet is then finally set to the current data. In addition the new over all duration is calculated and the result set to the work item’s duration.

Finally the time sheet is added to the work item’s dependent items. Updating the work items duration and adding the time sheet as dependent results in both being saved if the work item is saved.

A few remarks on the code.

  • I did this with RTC 4.0.1
  • For whatever reason the method setTimeCode() is deprecated. However, if I run the code against a 5.0.2 server and don’t use the method, the entry does not show up
  • To create the start date I use SimpleDateFornat and provide the data only down to the year, month and date, this way the entry is automatically created with a timezone and time that works for me
  • The work type is either provided and then used; otherwise the work item type is used as work type

If the start date is created with a wrong timezone and time, the entry might not show up in the UI. To fix that, look at the data used in your repository and change the creation of the start data accordingly.

The TimeTrackingHelper

To make it easier to use the code above, it is part of a class called TimeTrackingHelper. This helper class implements loading existing time sheet entries into a hash map structure that makes searching for the entry easier. The hash map should allow to iteratively use the helper on the same class if required. If the work item changes, the hash map is built up again.

The time tracking helper has code to convert text/string values to the elements needed for a time sheet. It also implements a pattern that allows to iterate the hash map and run an interface on each entry to print the data or do something else with it.

The example below shows how to use the helper in a WorkItemOperation to add a time sheet entry.

// TimeTracking
private static class ModifyTimeTracking extends WorkItemOperation {

	private String fTimeCode = null;
	private String fStartDate = null;
	private String fWorkHours = null;
	private String fWorkType;

	public ModifyTimeTracking(String timeCode, String startDate,
			String workHours, String workType) {
		super("Modify TimeTracking", IWorkItem.FULL_PROFILE);
		this.fTimeCode = timeCode;
		this.fStartDate = startDate;
		this.fWorkHours = workHours;
		this.fWorkType = workType;
	}

	@Override
	protected void execute(WorkItemWorkingCopy workingCopy,
			IProgressMonitor monitor) throws TeamRepositoryException {
		TimeTrackingHelper helper = new TimeTrackingHelper();
		helper.updateTimeTrackingInfo(workingCopy, fTimeCode, fStartDate,
				fWorkHours, fWorkType, monitor);
		helper.printTimeSheets(workingCopy.getWorkItem(), monitor);
	}
}

The interesting part is in the method execute(), where the helper is instantiated first and then used to update (or create) a time sheet.

The code below shows how the helper can be used to just print (or access) the time sheet data.

	int id = new Integer(idString).intValue();

	IWorkItemClient workItemClient = (IWorkItemClient) teamRepository
			.getClientLibrary(IWorkItemClient.class);
	IWorkItem workItem = workItemClient.findWorkItemById(id,
			IWorkItem.FULL_PROFILE, monitor);
	System.out.println("Accessing work item: " + workItem.getId() + ".");
	TimeTrackingHelper helper = new TimeTrackingHelper();
	helper.printTimeSheets(workItem, monitor);
	System.out.println("Accessed work item: " + workItem.getId() + ".");

The code for the TimeTrackingHelper the example class to modify the time tracking data of a work item called ModifyWorkItemTimeTracking as well as the class to just print the data AccessTimeTracking is shipped with the download.

Summary

This post shows how time tracking data is managed using the API. As always I hope this helps someone out there to get their job done more efficient.

12 thoughts on “The Work Item Time Tracking API

  1. Hello Ralph,
    I am Huan Feng MC Cai of GBS China.
    I found your blog is really helpful and I have some questions I want to ask you.
    Q1.
    >// Update the value
    >// Note: it is important to set the duration value, of the work item
    >// otherwise the work item is not marked as dirty and in need to update
    >// in the repository and the save process will not save the time sheet
    >getWorkItem().setDuration(getTotalDuration().longValue());
    I think it is not appropriate to set the value of Duration in this place, it should set the value of Time Spent.
    I have tested and found that it is feasible.
    ————————————————————
    IWorkItemClient workItemClient = (IWorkItemClient) ((ITeamRepository) getWorkItem().getOrigin()).getClientLibrary(IWorkItemClient.class);
    IAttribute attrIimeSpent = workItemClient.findAttribute(getWorkItem().getProjectArea(), “timeSpent”, null);
    getWorkItem().setValue(attrIimeSpent, getTotalDuration().longValue());
    ————————————————————

    Q2.
    >The only way to update the time sheet data is to force an update to the work item by setting the duration value.
    >This way the API detects that it has to save the work item as well as the time sheet entries as dependent items.
    I found that by setting the value of other items can also achieve this goal, and not just by setting the value of duration.
    e.g.
    Summary,Description etc.

    • Time spent is the analog to the duration in the formal template in the agile template.

      You can set any attribute you like to force saving the work item. It you only have the time available you use it.

      • Thank you very much for your reply.
        >Time spent is the analog to the duration in the formal template in the agile template.
        I’m not sure what you mean.
        Is that what you’re saying?
        —————————————————————————
        At the beginning:
        Time spent is the analog to the duration value.

        Version iteration :
        Estimate is the analog to the duration value.
        —————————————————————————

      • Sorry, there are several duration type attributes in work items. You are correct, I should have said time spent.

      • >You can set any attribute you like to force saving the work item. It you only have the time available you use it.
        Are you saying I’m right about Q2.
        ———————————
        I found that by setting the value of other items can also achieve this goal, and not just by setting the value of duration.
        e.g.
        Summary,Description etc.
        ———————————

  2. If you carefully look into the code you realize that I don’t actually use any attribute directly. I use

    getWorkItem().setDuration(getTotalDuration().longValue());

    so I can ignore what attribute that is.

  3. Hello Ralph,

    I gone through this article it helped me a lot.
    Using this article i am able to update or modify the time sheet. But the problem is that it is not visible at timesheet entries.

    Please help me.

    Thanks in Advance.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.