Handling Iterations – Automation for the “Planned For” Attribute


One thing that I started thinking about several times during the last few years is how to handle the “Planned For” or other iteration attributes during automation. I was looking into how to copy work items from one repository to another for example. It was not immediately obvious how to work with it and I always had other more pressing demands so that I never finished thinking about it. Some pretty big projects at IBM, with very mature and highly automated processes, that have migrated to RTC. I am helping some of them and the question came up again. This time around, I found the time to really look into it and I can share something. Here goes….

*Update* The post A RTC WorkItem Command Line Version 2 contains downloadable code that performs most of the activities required for reading and modifying work items, their attributes, and all kinds of links. This includes reading and writing work item attribute of all kinds, including the iteration attribute types. The interesting code can be found in the com.ibm.js.team.workitem.commandline.helper package in the class WorkItemHelper. All techniques described below are used there. You can familiarize yourself with the concepts in this post and then look into that project for how it is used.

* Update * I had to do manual changes to the code snippets below. So it might not compile. Use the downloaded code, instead of copying the code examples into your code.

* Update * Pugazhenthi Samidurai also published a neat solution here Jazz.net Forum question.

License and how to get started with the RTC API’S

As always, 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, which basically means you can use it for internal usage, but not sell. Please also remember, as stated in the disclaimer, that this code comes with the usual lack of promise or guarantee. Enjoy!

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 following code in this environment and get your own automation or extension working.

To keep it simple this example is, as many others in this blog, based on the Jazz Team Wiki entry on Programmatic Work Item Creation and the Plain Java Client Library Snippets. The example in this blog shows RTC Client and Common API.

Download

* Update *: The code can be downloaded from here. The code also has some enhancements over the code shown below, e.g. more options to compare.

About Iterations

If you look at iterations in the API they appear to be an ordinary item at the first glance. Looking deeper, they are a bit special. Like categories, iterations, together with development lines (timelines), compose a tree structure. Unlike categories there is no built in API that allows to find the iteration object based on the path. Categories can be found using code like this.

List path = Arrays.asList(categoryName.split("/"));
ICategoryHandle category = workItemClient.findCategoryByNamePath(projectArea, path, monitor);

There is no such API available for iterations. However, to display an iteration some means to get the path and create a string are required. In case a string, describing the path in the iteration structure, is available some means to find the related iteration is also required. To make it more complex an iteration and a development line have an ID as well as a display name. If someone wants to do a mapping based on some strings it would be nice to be able to map by a path by id as well as by name.

So I ended up developing some helper classes to support me with the effort. One helper class just creates a path from any given iteration up to the development line. The other helper tries to find an iteration based on a given path.

Lets look at the path helper, that creates a paths for a given iteration handle, first. The code I developed can be found below. Some explanations and how to use it follow.

/*******************************************************************************
 * Licensed Materials - Property of IBM
 * (c) Copyright IBM Corporation 2012.  
 * 
 * PathHelper
 * 
 * Note to U.S. Government Users Restricted Rights:  Use, 
 * duplication or disclosure restricted by GSA ADP Schedule 
 * Contract with IBM Corp.
 *******************************************************************************/
package com.ibm.js.team.api.tools.process;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;

import com.ibm.team.process.common.IDevelopmentLine;
import com.ibm.team.process.common.IDevelopmentLineHandle;
import com.ibm.team.process.common.IIteration;
import com.ibm.team.process.common.IIterationHandle;
import com.ibm.team.repository.client.IItemManager;
import com.ibm.team.repository.client.ITeamRepository;
import com.ibm.team.repository.common.IFetchResult;
import com.ibm.team.repository.common.TeamRepositoryException;

/**
 * Helps to create a path representation of iteration that build up a logical
 * path down to the root development line. Handles three parallel path
 * representations based on an path created from ID's, from a name and from a
 * label. Allows to output the path using a separator that can be customized. *
 * 
 * @author rschoon
 * 
 */
public class PathHelper {

	List fIdPath = null;
	List fNamePath = null;
	List fLabelPath = null;
	ITeamRepository fTeamRepository = null;
	IProgressMonitor fProgressMonitor = null;

	String fSeperator = "/";

	public PathHelper(ITeamRepository teamRepository,IProgressMonitor progressMonitor) {
		super();
		fTeamRepository = teamRepository;
		fProgressMonitor = progressMonitor;
	}

	private void initialize() {
		fIdPath = new ArrayList();
		fNamePath = new ArrayList();
		fLabelPath = new ArrayList();
	}

	/**
	 * Calculate the path for the iteration. Make paths available for ID's displaynames and labels 
	 * 
	 * @param handle
	 * @throws TeamRepositoryException
	 */
	public void calculateIterationPath(IIterationHandle handle)throws TeamRepositoryException {
		initialize();
		IIteration iteration = resolveIterationHandle(handle);
		getIterationPath(iteration);
	}

	/**
	 * @return the calculated path with names or null if the calculation never ran.
	 */
	public List getNamePath() {
		return fNamePath;
	}

	/**
	 * @return  the calculated path with ID's or null if the calculation never ran.
	 */
	public List getIdPath() {
		return fIdPath;
	}

	/**
	 * @return  the calculated path with labels or null if the calculation never ran.
	 */
	public List getLabelPath() {
		return fLabelPath;
	}

	/**
	 * @return the id path as string or null, if it was never created.
	 */
	public String toIDPathString() {
		return toPathString(fIdPath);
	}

	/**
	 * @return the label path as string or null, if it was never created.
	 */
	public String toLabelPathString() {
		return toPathString(fLabelPath);
	}

	/**
	 * @return the name path as string or null, if it was never created.
	 */
	public String toNamePathString() {
		return toPathString(fNamePath);
	}

	/**
	 * @return the seperator used to build up the string representation
	 */
	public String getSeperator() {
		return fSeperator;
	}

	/**
	 * Set a seperator string used to build up the string representation
	 * 
	 * @param fSeperator
	 */
	public void setSeperator(String seperator) {
		this.fSeperator = fSeperator;
	}

	/**
	 * Find the path to an iteration using the iteration handle
	 * 
	 * @param iteration
	 * @throws TeamRepositoryException
	 */
	private void getIterationPath(IIteration iteration) throws TeamRepositoryException {
		this.add(0, iteration.getId(), iteration.getName(),iteration.getLabel());
		IIterationHandle parentHandle = iteration.getParent();
		if (parentHandle != null) {
			// Recurse into the parent
			IIteration parent = resolveIterationHandle(parentHandle);
			getIterationPath(parent);
		} else {
			IDevelopmentLineHandle devLineHandle = iteration.getDevelopmentLine();
			List handles = new ArrayList();
			handles.add(devLineHandle);
			IFetchResult result = getTeamRepository().itemManager().fetchCompleteItemsPermissionAware(handles,
				IItemManager.REFRESH, getProgressMonitor());
			IDevelopmentLine developmentLine = (IDevelopmentLine) result.getRetrievedItems().get(0);
			add(0, developmentLine.getId(), developmentLine.getName(),developmentLine.getLabel());
		}
	}

	/**
	 * Get the iteration from its handle
	 * 
	 * @param handle
	 * @return
	 * @throws TeamRepositoryException
	 */
	private IIteration resolveIterationHandle(IIterationHandle handle) throws TeamRepositoryException {
		List handles = new ArrayList();
		handles.add(handle);
		IFetchResult result = getTeamRepository().itemManager().fetchCompleteItemsPermissionAware(handles,
			IItemManager.REFRESH, getProgressMonitor());
		return (IIteration) result.getRetrievedItems().get(0);
	}

	private IProgressMonitor getProgressMonitor() {
		return fProgressMonitor;
	}

	private ITeamRepository getTeamRepository() {
		return fTeamRepository;
	}

	/**
	 * Add a path segment to the path
	 * 
	 * @param i
	 * @param id
	 * @param name
	 * @param label
	 */
	private void add(int i, String id, String name, String label) {
		fIdPath.add(i, id);
		fNamePath.add(i, name);
		fLabelPath.add(i, label);
	}

	/**
	 * @param list
	 * @return
	 */
	private String toPathString(List list) {
		boolean first = true;
		String outStr = "";
		for (Iterator iterator = list.iterator(); iterator.hasNext();) {
			String name = (String) iterator.next();
			if (first) {
				first = false;
			} else {
				outStr += fSeperator;
			}
			outStr += name;
		}
		return outStr;
	}
}

The code can be called like below.

PathHelper ph = new PathHelper(teamRepository, monitor);
ph.calculateIterationPath(handle);
String paths = "IDPath: [" + ph.toIDPathString() + "] NamePath: (" + ph.toNamePathString() + ")";

Basically you create a new PathHelper and use the new object to call PathHelper.calculateIterationPath(handle) to calculate the paths for ID and name. The path helper stores both paths and makes them accessible using PathHelper.getNamePath() or PathHelper.getIdPath(). You can also get the string representation using PathHelper.toIDPathString() or PathHelper.toNamePathString(). The string is simply a string of the format "development/Release 1.0/Sprint 2". This is a string containing the name or ID of the development line and the iterations separated by a separator string. The separator can be changed if needed.

The PathHelper class basically allows to read and analyze iterations. Now it would be desirable to be able to locate an iteration based on a given path. The code below finds a development line based on the path.

/*******************************************************************************
 * Licensed Materials - Property of IBM
 * (c) Copyright IBM Corporation 2012.  
 * 
 * DevelopmentLineHelper
 * 
 * Note to U.S. Government Users Restricted Rights:  Use, 
 * duplication or disclosure restricted by GSA ADP Schedule 
 * Contract with IBM Corp.
 *******************************************************************************/
package com.ibm.js.team.api.tools.process;

import java.util.List;

import org.eclipse.core.runtime.IProgressMonitor;

import com.ibm.team.process.common.IDevelopmentLine;
import com.ibm.team.process.common.IDevelopmentLineHandle;
import com.ibm.team.process.common.IIteration;
import com.ibm.team.process.common.IIterationHandle;
import com.ibm.team.process.common.IProjectArea;
import com.ibm.team.process.common.IProjectAreaHandle;
import com.ibm.team.repository.client.IItemManager;
import com.ibm.team.repository.client.ITeamRepository;
import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.workitem.client.IAuditableClient;
import com.ibm.team.workitem.common.model.ItemProfile;

/**
 * Tries to find a development line and enclosed iteration for a project area. 
 * 
 * @author rschoon
 *
 */
public class DevelopmentLineHelper {

	private ITeamRepository fTeamRepository;
	private IProgressMonitor fMonitor;
	private IAuditableClient fAuditableClient;

	public DevelopmentLineHelper(ITeamRepository teamRepository, IProgressMonitor monitor) {
		fTeamRepository = teamRepository;
		fMonitor = monitor;
	}

	/**
	 * Find a development line based on the path provided.
	 * 
	 * @param projectArea
	 * @param path
	 * @param byId search by id or name
	 * @return a development line found or null.
	 * @throws TeamRepositoryException
	 */
	public IDevelopmentLine findDevelopmentLine(IProjectArea projectArea,
			List path, boolean byId) throws TeamRepositoryException {
		int level = 0;
		String lookFor = path.get(level);
		IDevelopmentLineHandle[] developmentLineHandles = projectArea
				.getDevelopmentLines();

		for (IDevelopmentLineHandle developmentLineHandle : developmentLineHandles) {
			IDevelopmentLine developmentLine = fAuditableClient
					.resolveAuditable(developmentLineHandle,
							ItemProfile.DEVELOPMENT_LINE_DEFAULT, fMonitor);
			String compare = "";
			if (byId) {
				compare = developmentLine.getId();
			} else {
				compare = developmentLine.getName();
			}
			if (lookFor.equals(compare)) {
				if (path.size() > level + 1) {
					IIteration found = findIteration(iteration.getChildren(),
						path, level + 1, byId);
					if (found != null) {
						return found;
					}
				} else {
					return iteration;
				}
			}
		}
		return null;
	}
}

Now we have the development line, we can find the iteration with the following code:

/**
 * Find an iteration based on the path provided.
 * 
 * @param iProjectAreaHandle
 * @param path
 * @param byId
 * @return an iteration if one can be found or null otherwise
 * 
 * @throws TeamRepositoryException
 */
public IIteration findIteration(IProjectAreaHandle iProjectAreaHandle,
		List path, boolean byId) throws TeamRepositoryException {
	fAuditableClient = (IAuditableClient) fTeamRepository
			.getClientLibrary(IAuditableClient.class);
	IIteration foundIteration = null;
	IProjectArea projectArea = (IProjectArea) fTeamRepository.itemManager()
			.fetchCompleteItem(iProjectAreaHandle, IItemManager.REFRESH,
					fMonitor);
	IDevelopmentLine developmentLine = findDevelopmentLine(projectArea,
			path, byId);
	if (developmentLine != null) {
		foundIteration = findIteration(developmentLine.getIterations(),
				path, 1, byId);
	}
	return foundIteration;
}

The code uses the following method inside, to find the iteration.

/**
 * Find an Iteration
 * 
 * @param iterations
 * @param path
 * @param level
 * @param byId
 * @return
 * @throws TeamRepositoryException
 */
private IIteration findIteration(IIterationHandle[] iterations,
		List path, int level,  boolean byId)
		throws TeamRepositoryException {
	String lookFor = path.get(level);
	for (IIterationHandle iIterationHandle : iterations) {

		IIteration iteration = fAuditableClient.resolveAuditable(
				iIterationHandle, ItemProfile.ITERATION_DEFAULT, fMonitor);
		String compare = "";
		if (byId) {
			compare = iteration.getId();
		} else {
			compare = iteration.getName();
		}
		if (lookFor.equals(compare)) {
			if (path.size() > level + 1) {
				IIteration found = findIteration(iteration.getChildren(),
						path, level + 1, byId);
				if (found != null) {
					return found;
				}
			} else {
				return iteration;
			}
		}
	}
	return null;
}

The DevelopmentLineHelper class can be used like below.

IIteration found = devLineUtil.findIteration(workItem.getProjectArea(), ph.getIdPath(), true);
if (found == null) {
	found = devLineUtil.findIteration(workItem.getProjectArea(), ph.getNamePath(), false);
}
if (found != null) {
	log("Found:" + found.getLabel());
	if(found.hasDeliverable()){
		workItem.setTarget(found);
	}
}

This code uses the DevelopmentLineHelper to try to match the path for the iteration based on ID first. If you are working in the same project area this should work. If not, you might want to check if the path by name matches.

If the iteration can be located, and is marked as a deliverable, you can set it as Planned for the work item.

In case you only have a string representation you can use the following code to use the DevelopmentLineHelper the code can be downloaded from here.

String iterationPath = "DevLine/Release 1/Iteration 1";
List path = Arrays.asList(iterationPath.split("/"));
// map by ID
IIteration found = devLineUtil.findIteration(projectArea, path , true);
if (found == null) {
	IIteration found = devLineUtil.findIteration(projectArea, path , false);
}

As always, to keep it simple, there is few error handling. You might want to enhance this for later usage. I hope the code helps someone out there to save some time and get the work done.