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.

Advertisements

About rsjazz

Hi, my name is Ralph. I work for IBM and help colleagues and customers with adopting the Jazz technologies.
This entry was posted in Jazz, RTC, RTC Extensibility and tagged , , , , , . Bookmark the permalink.

36 Responses to Handling Iterations – Automation for the “Planned For” Attribute

  1. lioramilbaum says:

    Hi Ralph,
    I am educating myself using your posts. Thanks for that.
    One question regarding the current post. Is the code intended for a standalone java application or an eclipse plug-in?
    If it is used in a standalone application, than I should connect to a specific project area and than use it.

    Liora

  2. lioramilbaum says:

    Thanks Ralph. This is what I thought but I wasn’t sure.
    I have already implemented the Update Parent Duration Participant but never implemented a client tool using the plain Java client libraries. I will figure it out.

    • rsjazz says:

      Hi Liora, you might want to look here: https://jazz.net/library/article/807 for some additional hints on setting up your environment. If you have set up RTC with the SDK already, you simply add the plain java client libraries as described in the article.

      As an alternative, if you want to use the code in a participant, you would have to look up the server API. I will look at it as soon as I have time to and publish what I found.

    • rsjazz says:

      A last thought, the code in the post uses the ID’s or the names. Actually I think it would be better to use the label instead of the name. The label is the name of the iteration and the ID if there is no name.

  3. lioramilbaum says:

    I do want to look at the specified article and try to implement it on my education env.
    Thanks for the hints. 🙂
    After this one, I’ll get back to the current post and check if it is working for me.

  4. lioramilbaum says:

    Ralph, it works!!! Now I am ready for this post. 🙂

  5. Thomas Immel says:

    Sorry but the code don’t compile, becaue of the findIteration() method is not included in the code sample.

  6. Amit Kumar says:

    Hi Ralph,
    How can I get the list of all iterations under a project area ?
    Thanks,
    Amit

    • rsjazz says:

      This is shown int the code above in findDevelopmentLine(). However, the code in the post was messed up for some reason. I fixed that right now. The download has the correct code, however.

      IDevelopmentLineHandle[] developmentLineHandles = projectArea
      .getDevelopmentLines();

      • rsjazz says:

        I don’t know what happened, but when writing the post a lot of the code did not make it into the post. I fixed that. The download should have all the code however and the code is enhanced for another compare mode.

  7. Clement Liu says:

    Hi Ralph,

    Is there any way to archive an iteration by using the client API?

    Thanks.

    • rsjazz says:

      Clement, I haven’t done this so far. A quick look (search the workspace for setArchived()) shows that IIteration does not expose setArchived(), however Iteration does. Not sure you can cast an IIteration to Iteration – I’d try. Some unit test code I found:

      Iteration iteration = (Iteration) IIteration.ITEM_TYPE.createItem();
      iteration.setId(“iteration” + count);
      iteration.setName(“Iteration ” + count++);
      iteration.setArchived(isArchived(item));

  8. Steven Hovater says:

    Suppose we could just create the iteration id using the id of the parent(s), i.e.

    development#release 3.0#iteration 1

    Parsing the path is trivial.

  9. Hi there, I want to subscribe for this web site to obtain newest updates,
    therefore where can i do it please assist.

    • rsjazz says:

      I was able to follow some blogs here, I think the follow button was available somewhere. There are people following the blog. However, I added a follow widget above the feed widget.

  10. BalaMurugan S says:

    Hi Ralph, How to check whether a “Planned For” changed while saving the work item.

  11. ASalme says:

    Ralph, how can I assign an existing projectarea developmentline to a teamarea through the java api?

    • rsjazz says:

      Look at com.ibm.team.process.common.ITeamAreaHierarchy.setDevelopmentLine(ITeamAreaHandle, IDevelopmentLineHandle)

      and

      com.ibm.team.process.common.IProjectArea.setDevelopmentLines(IDevelopmentLineHandle[])

      and

      com.ibm.team.process.common.IProjectArea.setProjectDevelopmentLine(IDevelopmentLineHandle)

  12. Ralph, how can I assign set “Unassigned” value for Planned For Field ? Is it possible any way ?

    • rsjazz says:

      Have you tried to set null? That is what I see as value when it is unassigned.

      It is always a good ides to look into what data the tool contains.

      • Ralph,
        Thanks for your quick response. I tried with target:set=”” but no luck . I get this exception

        ERROR: Exception getting attribute representation: [target] Value: []

        Please suggest if I missed something.
        Thanks,

      • rsjazz says:

        This does not make any sense as it is no Java syntax.

        I also said to try to set null, not an empty string. I just set the attribute “target” to null and the work item now shows Planned for as unassigned.

        Before asking such questions, read the value in the tool and try to figure how that could get in there.

        My code:
        IAttribute target = workItemClient.findAttribute(workItem.getProjectArea(), fAttributeId,monitor);
        if (workItem.hasAttribute(target))
        workItem.setValue(target, null);

      • Thanks Ralph, I change the code and pass null value for target and now this is working fine :). Thanks for your help. You are the genius on Jazz.

        Thanks once again.

      • rsjazz says:

        Great it is now working for you.

  13. Ralph, I used this “target:set=”” ” on wcl command line execution mode and pass this value as command line parameter. After that I checked the code and found that set operation is not supported for Iteration. so immediately I changed the code and make it workable. .
    Thank you ,

    • rsjazz says:

      Ah, OK. I don’t really know if I ever tested this in the WCL, to be honest. I will have to check. Thanks for the hint.

      • rsjazz says:

        Out of curiosity, is the WCL code manageable for you? It basically grew over time and I never had time to restructure anything.

      • Yes Ralph, this is working fine I have changed some of the part like thorough command line execution on Windows user can provide maximum 32k char as input param. So I take file as input parameter if input car more then 32k char.

        But overall this is one of the good solution for testing and quick debugging.

        Thanks,
        Debasish

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 )

Google+ photo

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

Connecting to %s