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.

44 thoughts on “Handling Iterations – Automation for the “Planned For” Attribute

  1. 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. 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.

    • 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.

    • 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. 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.

    • 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();

      • 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.

    • 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));

  4. 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.

    • 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.

    • 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)

      • 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,

      • 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.

  5. 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 ,

      • 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

  6. Hello Ralph, I have a query which list some work item ids as result and I’m iterating each work item attribute to generate some report using Plain Java API. I’m referring this document/link to get the “planned for” attribute for each work item and trying to understand how to get the IIterationHandle. is there anyway I could resolve IIterationHandle ?

    • This attribute type works pretty much as any other.

      You can get a value that might be null or a handle of some sort, you can set such a matching value. This explains the basics: https://rsjazz.wordpress.com/2013/01/02/working-with-work-item-attributes/ that post and this post also hints what you should read to get up to speed.

      You can resolve them like any other handle and the blog post actually shows the code how to to anyone who can properly use a browser search.

      The attached helper code you can download should explain pretty much anything else you need to know.

      Thanks for reading the post.

  7. Hello Ralph,
    I am using WCL 5.2 to import a csv with “Planned For” and using a mapping File. I would like ti import the workitems in a perticular Plan/Iteration so that the Kanban Board is populated with the workItems.
    The iteration is created as “Timeline/It1”
    I am getting the following exception. Please help me resolve this with WCL.
    com.ibm.js.team.workitem.commandline.framework.WorkItemCommandLineException: Exception getting attribute representation: ‘target’ Value: ‘/Timeline/It1’. Original exception:
    Iteration not found: ‘target’ Value: ‘/Timeline/It1’.

    • I don’t know what the issue is.

      From the documentation an iteration:

      Iteration: Iteration – specified by its name path (including the development line name).

      Example: “Iteration:Main Development/Release 1.0/Sprint 3”

      Where ‘Main Development’ is the name of the timeline. Assuming that your timeline is named Timeline – which I find suspicious – I would expect Timeline/It1 to work

      It would also be a good idea to try to export first and see what is exported or to try to otherwise to understand the values required.

      • Hi Ralph,
        Command :
        importworkitems /importdebug user=”abc” password=”xyz$” projectArea=”Kanban Board” importFile=”D:\HistoryData\JazzData.csv” repository=”repo link” mappingFile=”D:\HistoryDataUpdated\Jazz_Attributes01.xml” delimiter=”,”

        Value of Planned For in csv : PI 1
        Iteration created in Timeline is : /program-timeline/roadmap/art/pi1/pi1-s1.1

        mapping file:

        Following is the exception:
        com.ibm.js.team.workitem.commandline.framework.WorkItemCommandLineException: Exception getting attribute representation: ‘target’ Value: ‘/program-timeline/roadmap/art/pi1/pi1-s1.1’. Original exception:
        Iteration not found: ‘target’ Value: ‘/program-timeline/roadmap/art/pi1/pi1-s1.1’.

        As per your suggestion I exported items from a plan . The value exported is :Planned For
        PI 1

      • Three things:

        1. This and your other comment does not belong here at this blog. The blog talks about the planned for API. Next time ask at the WorkitemCommandLine blog posts or in an issue on Github: https://github.com/jazz-community/work-item-command-line

        2. I have already answered you with the most likely correction THERE IS NO SLASH IN FRONT OF THE NAME OF THE TIMELINE. See the example in my answer. A valid value would look like “Main Development/Release 1.0/Requirements” as far as I can tell. The example in my response shows that. I don’t have time to configure a full blown import, but that format is consistent with the documentation.

        3. The WCL prints a help if no command is provided that you can direct into a file and read. I believe the format for iterations is mentioned there. It is definitely in my answer and in the readme.me here: https://github.com/jazz-community/work-item-command-line .

        WCL provides several means to print work items and export them. It is always a smart approach to print or export something, if one is not sure about how the values should look like e.g. for an import. WCL tries to use the RTC built in export formats where that is possible, so an export there would likely helped as well.

Leave a comment

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