Work Item Command Line 4.1


I recently had a chat with a colleague who is trying to automate RTC Work Item creation. As a casual user without administration experience they where wondering how to find the IDs for the work item types to be able to create work items of the types in WCL. Today you must know the ID of the work item and look them up in the administration UI.

I always wanted to add the ability to be able to list the available work item types in WCL, but for whatever reason I never got to searching the API and add it to WCL.  Having some time last week I took the opportunity to finally implement it. This closes a last big gap that WCL left open. Users can now print the work item types and with that information print the attributes available on the work item type.

A small description how to use WCL can be found here. WCL prints a help if the command is omitted. The help information is very long. Make sure to redirect it into a file or increase the shell buffer size.

This post provides the code for the latest version of WCL and also a description how to install WCL.

License

WCL is released under the MIT License. See the License.txt and the license headers in the individual files.

Compatibility

This code has been used with RTC 4.x, 5.x  and 6.x with no or minimal changes and it is pretty safe to assume, that the code will work with newer versions of RTC to come. The code requires the RTC Plain Java Client Libraries. The Export and import commands require additional external libraries that need to be downloaded and installed separately. See the document below.

Download

Source Code for Download

The code is available in the Jazz Community. WCL 4.1 is available in this release.

Project

Additional Download

You can also download the latest version 4.1 here:

Changes

Added a command printtypes to list the work item types available for a project area.

wcl -printtypes repository="https://clm.example.com:9443/ccm" user=ralph password=****** projectArea="JKE Banking (Change Management)"

lists the work item types. The output will look like below:

printtypes

The Type ID is the work item type ID required to set and get work item information. The type category is an additional information to understand if work item types have the same workflow.

Download the Plain Java Client Libraries

WCL requires at least the RTC Plain Java Client Libraries for your version of RTC. To download that, open the All Downloads tab of the RTC version you are interested in. For example https://jazz.net/downloads/rational-team-concert/releases/6.0.5?p=allDownloads and scroll down to the Plain .zip Files section.

PlainJava

Download the RTC Plain Java Client Libraries file.

Use 7Zip and unzip the RTC Plain Java client Libraries download file (for example named RTC-Client-plainJavaLib-6.0.5.zip). Use 7Zips Extract Files command and provide the extraction Path for example C:\RTCWCL\PlainJavaAPI. Remember the location for later.

Optional: Download the Plain Java Client Libraries API documentation and extract it to the same folder you extracted the RTC Plain Java client Libraries download.

How to Install WCL

There are several ways how the WCL can be set up and run. The two main methods are:

  1. As pre-compiled Java e.g in a jar file using a batch file
  2. Using launch files to run or debug in Eclipse

The following sections explain the first method.

Create Packaged Version

To package your own version of WCL for release from the source code, setup your development environment as described in the next section. Then follow the document ReadMe – HowToRelease.txt in the eclipse project com.ibm.js.team.workitem.commandline you can find in the source code to package WCL.

Install Packaged Version

If you download the packaged, executable application latest version 4.1, the download is already packaged and prepared. The downloaded file is compressed and will be named like WCL_V4.1_20180323.zip. Extract the WCL package file e.g. using 7Zip to a folder, for example C:\RTCWCL.

The folders structure should look as below.

WCLCommandLine

Check and Adjust the Script File

To work, the WCL needs a Java JRE or JDK. Open the file wcl.bat or wcl.sh. Provide a JRE or JDK. I Usually use a JRE that comes with the CCM server or a JDK that comes with the Eclipse client. However any compatible JRE should do.

Change the JAVA_HOME variable to point to a location containing a JRE. If needed, remove the path section  JRE from the final call.

BatchFile

If you installed the Plain Java Client Libraries API into a different location, set the variable PLAIN_JAVA to that folder location.

Make sure the files are executable and test WCL e.g. by calling wcl and running one of the commands. For example use the new prittypes command

wcl -printtypes repository="https://clm.example.com:9443/ccm" user=ralph password=****** projectArea="JKE Banking (Change Management)"

 

Provide Additional Libraries

The export and the import commands of WCL need two libraries that are not shipped with the downloads.

If you use the packaged WCL and want to use the export/import capability follow the steps below to add the required libraries to the folder lib in the folder lib in the WCL folder.

If you use the Eclipse project for WCL and want to use the export/import capability follow the steps below to add the required libraries to the folder lib in the Eclipse project com.ibm.js.team.workitem.commandline.

The export and the import commands of WCL use the Open CSV Library. I had issues with the newer versions of Open CSV that I could not resolve, so this code assumes the version 3.7. Download the version 3.7 from here. Uncompress and untar the the file opencsv-3.7-src-with-libs.tar.gz you downloaded. Look for the folder opencsv-3.7 deploy\ copy the JAR file opencsv-3.7.jar and put it into the lib folder of your version of WCL.

It is possible to use other such libraries like SuperCSV with minor changes to the source code as well.

The import commands of WCL can only provide the capability to use a mapping file by using a JAR file that only ships with the RTC Eclipse client and the SDK. The classes used for the mapping file capability are located in the library com.ibm.team.workitem.rcp.core.  Open the Install location of the RTC Eclipse client and search for com.ibm.team.workitem.rcp.core*. You should find a file names similar to this one: com.ibm.team.workitem.rcp.core_3.1.900.v20141010_0124.jar. The version numbers at the end could be different. Copy the JAR file into the into the lib folder of your version of WCL.

Installing the Source Code

The best approach to develop for the RTC Java APIs is, to setup the development environment based on the RTC SDK . This provides with the source code for the API and allows to search examples. This saves so much time in the long run that saving the effort of setting the SDK makes no sense. So follow the instructions in the next section.

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.

Importing the Source Code

Get the source code from the Jazz Community. Use Git to clone the project and import the source project into Eclipse. You can also download the source as a zip file and import the project with the other Eclipse Import wizards. Switch to the Java perspective or the Plug in Development perspective.

The project should look as below

SorceProject

The folders contain the information to build the jar file, to run the and debug the code, folder for additional libraries and the scripts to run the Java application from a shell.

The Eclipse project is a Plug-in development project. This trick provides the access to the SDK and the API source code.

Install The Plain Java Client Libraries

The project references the Plain Java Client Libraries and needs them to run.

BuildPath

So the minimum additional step needed to be able to run the WCL for development in Eclipse is to install the RTC Plain Java Client Libraries as a user library.  The Plain Java Client Libraries have already been downloaded and installed. The same folder with the install can be used in the next steps.

Open Windows>Preferences and type “User lib” into the search window.

BuildPath_2

In the Java>Build Path User Libraries click the New… button. Type the name that is alreay referenced: PlainJavaAp. Click OK.

Select the new User Library and click Add External JARs…

BuildPath_3

Browse to the folder containing the Plain Java Client Libraries C:\RTCWCL\PlainJavaAPI in this example. Select all JAR files and click open.

BuildPath_4

The User Library should now contain the libraries.

BuildPath_5

Go back to the section “Provide Additional Libraries” and add the libraries to the folder lib. Make sure the libraries are available. Remove and re add the jar files to the build path if needed.

You should now be able to run or debug the WCL from within Eclipse. The debug configurations shipped with the source code in the Launches folder are now available under the Debug>Debug Configuration section.

Launches

Change the configurations as needed.

To package your own version of WCL for release from the source code, follow the document ReadMe – HowToRelease.txt in the eclipse project com.ibm.js.team.workitem.commandline you can find in the source code to package WCL.

Summary

You should now be able to start working on WCL and enhance it if needed. I hope this document helps the many users out there.

 

 

 

 

 

Advertisements

Status History Presentation for RTC


Rational Team Concert (RTC) has a built-in feature to view the history of a work item. But especially for work items with many changes, it is hard to follow the Status of a work item over time. That’s why Lukas created the amazing Status History Presentation for RTC and published it in the DACH Jazz Community project. It shows all Status changes since the creation of the work item in form of a timeline in the RTC work item Web UI.

Follow the instructions in the Status History Presentation for RTC project to download and install the editor presentation server extension. The Open Source project also serves as a great example for how to write a custom editor presentation.

After installing this extension plug-in into your RTC instance (server side), you will be able to add a “Status History” presentation on your work item editors (WEB only). You can add this  presentation to a work item editor section for example the Quick Info section. Once the presentation is available it will

  • Show all state changes in a timeline
  • Show who did the state change
  • Show the number of days a work item was or is in a state
  • A Rich Hover shows additional information about the changes made together with the state change

Many thanks to the DACH Jazz Community for sharing their amazing work.

JavaScript Attribute Customization – Where is the log file?


A lot of users try Java Script based attribute customization and often run into issues. They ask on the Jazz.net forum to get the issue solved. Unfortunately the questions usually lack the information required to help. This post explains how to retrieve log information to be able to provide this information.

Where are the Script Log files?

Java Script attribute customization can use the console to log text messages into a log file.

console.log("My message");

The question is, where are the log files?

The script context

Java Script attribute customization scripts are, as far as I can tell, run in one of the following contexts:

  1. The Eclipse Client
  2. The Web Browser
  3. The RTC Server

Dependent on the context it is run, the log information can be found in a log file that is created and maintained by the

  1. The Eclipse Client
  2. The RTC Server

Please note that the logging information is not in the RTC Application log file CCM.log.

The Jazz.net Wiki entry about attribute customization provides hints about how to log data and how to log data and how to debug scripts in the section Debugging Scripts. Similar information is provided in the Process Enactment Workshop for the Rational solution for Collaborative Lifecycle Management.

Unfortunately both only talk about how to find the server log information for Tomcat. Since Websphere Application Server and WAS Liberty are also valid options, how can one find the log files in this case?

Find The Eclipse Workspace Log

As background, note that the Eclipse Client as well as the RTC Server are based on Eclipse technology. This common technology is used to log the data and determines the log file location and name.

Each running Eclipse has a workspace location and stores meta data and log information in this workspace. The workspace is basically a folder in the file system. The metadata is stored in a sub folder with the name .metadata. The log file is in this folder and named .log.

For the RTC Eclipse client and for scripts that run in this context, open the Eclipse workspace folder that is used and find the .log file in the .metadata folder.

For the RTC Server, the easiest way to find this workspace and the enclosed log file that I have been able to find is to search for the folder .metadata. For Tomcat and WAS Liberty standard installs go to the folder where the RTC Server was installed and then into the sub folder server. From here search for the folder .metadata.

For Websphere Application Server (WAS) go the profile folder for the profile that includes the RTC server deploy and search there.

Here an example search for a test install based on WAS Liberty:

LogFileLocations_2016-06-17_13-10-14

Note that every Jazz application has its own Eclipse workspace with metadata folder and workspace log file. The one interesting for RTC attribute customization is the workspace of the RTC server. The folder structure includes the context root of the Jazz application. Each application has a different context root which typically matches the application war files name prefix. The RTC application typically has the context root and application war files name prefix ccm. Open the workspace for this application and find the log file.

Looking Into the Log File

You can look into the log file. Please make sure to use a tool that does not block writing to the log file, while you are browsing its content. The log file is kept open by the server when it is running and blocking it from writing is not what is desired. Use more or an editor that reads the file and does not block it. For windows users: notepad does lock the file for writing. Use a different tool such as notepad++.

The Process Enactment Workshop for the Rational solution for Collaborative Lifecycle Management provides some examples for how logs look like and can be created. If you can’t find the log entry you are looking for, always check the server log as well. Maybe the script runs in a different context than you expect.

Here an example for log entries:

Log_Examples_2016-06-17_14-58-43

Load Errors

It might happen, that an expected log entry is not found in any of the log files. In this case make sure to check for script loading errors as well as thrown exceptions at the time the script was supposed to run.

Load errors can be caused by different reasons.

One reason can be that attachment scripts are not enabled. There are enough indicators in the Attribute customization editor in Eclipse that a user should have spotted this these days.

Another reason can be that the script is syntactically not correct and can not be interpreted as a valid JavaScript. One reason for a script not being recognizable as a valid script that I have seen recently is an incorrect encoding. If an external editor is used to edit the script and the script is then loaded from the file, make sure that the script has a correct UTF-8 encoding. If in doubt change the encoding to UTF-8 and reload the script.

Why would the encoding be important? The encoding controls the format of the content. it is hard to determine the encoding from a file and it is often not checked. But expecting a specific encoding but loading a file that was encoded in a different one can cause to find unexpected content. This can can cause the JavaScript not being recognized as JavaScript and the load fails.

Debugging vs. Logging

Using the debugging techniques explained in the Wiki entry in the section Debugging Scripts and in the Process Enactment Workshop for the Rational solution for Collaborative Lifecycle Management should be the preferred option and is usually more effective.

Looking at the logs is still a valid option, especially to be able to log execution times and to find script loading issues and for scripts that are run in the background in the server context, such as conditions.

I found using the Chrome Browser and the built in Developer Tools to be most effective. The scripts can easily be found in the sources tab under the node (no domain). Make sure to enable the debug mode as explained here: Debugging Scripts.

JavaScript_Debug_Chrome_2016-06-17_14-24-43

Summary

This post explains how to find the log files that contain log information written by JavaScript attribute customization scripts. I hope that this helps users out there and makes their work a little bit easier.

Setting Access Control Permissions for Work Items


This is the second post in the series around very fine grained access control permissions for work items and RTC SCM versionables. It explains how to set the access control permissions for work items.

See the problem description in the first post of the series

Related posts

The posts in this series are:

  1. Manage Access Control Permissions for Work Items and Versionables
  2. Setting Access Control Permissions for Work Items – this post
  3. Setting Access Control Permissions for SCM Versionables
  4. Setting Attributes for SCM Versionables

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. Enjoy!

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

Compatibility

This code has been used with RTC 5.0.2 and is prepared to be used with RTC 6.0.x with no changes and it is pretty safe to assume, that the code will work with newer versions of RTC.

The code in this post uses common libraries/services that are available in the Plain Java Client, Eclipse client and Jazz Eclipse server API. If client or server API is used, this is stated.

Work Item Access Control Code

Now that we have all the rules and pieces together as explained in this post, lets have a look at the work item access control. Again, note, that work items can only have access groups and project areas set for access control in manual mode as shown in the image below.

WorkItemAccessContextSelection

To set the access context for work items, the interface IWorkItem provides the method

IWorkItem.setContextId(UUID contextId);

It should also be possible to set the internal attribute with the ID “contextId” also available as constant

IWorkItem.CONTEXT_ID_PROPERTY

in the IWorkItem interface.

Working With the UUID Representing the Access Context

How to find the objects we are interested in, is explained in this post.

To be able to set the access control context for work items, it is necessary to understand how to access the value that represents the access context of a process area (project or team area) or access group.

The common API provides this method to access the access context of an item:

com.ibm.team.repository.common.IAuditableHandle.getItemId()

This interface implements (through a number of intermediate interfaces) the interface IItemHandle, which defines the method getItemId() for all items. So strictly speaking the method is available as

com.ibm.team.repository.common.IItemHandle.getItemId()

This means, the access context can be accessed already from the item handle e.g. in case only a handle such as IProcessAreaHandle, IProjectAreaHandle, ITeamAreaHandle, IAccessGroupHandle or IContributorHanlde is available.

It is also available at all resolved IItems such as IProcessArea, IProjectArea, ITeamArea, IAccessGroup or IContributor as all these interfaces implement IItemHandle. In fact it is available on all IItems. Example code:

IProcessAreaHandle paHandle = ..... get the handle
IProcessArea pa = ..... resolve the handle

// These values are the same
UUID paHandleContext1 = paHandle.getItemID(); 
UUID paHandleContext2 = pa.getItemID();

Note, that all items in RTC have an itemID like the one above and can be uniquely be identified by it. Other itemID’s are just not used as access context.

The return value is of the type

com.ibm.team.repository.common.UUID

The UUID is an object that uniquely identifies an item in the repository and can be used to recreate the item as well. The string representation shows up in several places, for example in OSLC links. To get the string representation use

com.ibm.team.repository.common.UUID.getUuidValue()

If it is necessary to recreate an UUID from the string representation as an example named contextIDString, use the method

UUID.valueOf(contextIDString)

This recreates the UUID of the object if the string is a valid string representation of a UUID.

This way, you have the choice to use the UUID or its string representation to set the access context of RTC work items.

Setting the WorkItem Access Context

Given the UUID provided by a project area or access group, it is possible to set the access context of a work item.

Although all the code to retrieve the access context is common code, the code to set the work items context differs on client and server.

In the Client API, use a WorkItemOperation like the code below.

/*******************************************************************************
 * Licensed Materials - Property of IBM
 * (c) Copyright IBM Corporation 2015. All Rights Reserved. 
 *
 * 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.access.control.client;

import org.eclipse.core.runtime.IProgressMonitor;

import com.ibm.team.repository.common.TeamRepositoryException;
import com.ibm.team.repository.common.UUID;
import com.ibm.team.workitem.client.WorkItemOperation;
import com.ibm.team.workitem.client.WorkItemWorkingCopy;
import com.ibm.team.workitem.common.model.IWorkItem;
import com.ibm.team.workitem.common.model.ItemProfile;

/**
 * WorkItem Operation to set the access context for the work item. This is
 * client code only.
 * 
 */
public class SetAccessControlOperation extends WorkItemOperation {

	private UUID fAccessContext;

	public SetAccessControlOperation(UUID accessContext,
			ItemProfile loadProfile) {
		super("Set Access Context", loadProfile);
		fAccessContext = accessContext;
	}

	@Override
	protected void execute(WorkItemWorkingCopy workingCopy,
			IProgressMonitor monitor) throws TeamRepositoryException {
		workingCopy.getWorkItem().setContextId(fAccessContext);
	}
}

 

The operation can be called like this:

	SetAccessControlOperation operation = new SetAccessControlOperation(
		UUID.valueOf(context), IWorkItem.FULL_PROFILE);
	operation.run(workItem, monitor);

Because the WorkItemOperation is not available in the server API, a save has to be performed manually like below.

	// Set the work item visibility
	// Get the full state of the parent work item so we can edit it
	// and avoid stale data exceptions
	// Get the required service interfaces
	IWorkItemServer wiServer = getService(IWorkItemServer.class);
	IWorkItem fullItem = (IWorkItem) wiServer
			.getAuditableCommon()
			.resolveAuditable(workItem, IWorkItem.FULL_PROFILE, monitor)
			.getWorkingCopy();

	// Set the context of the work item
	fullItem.setContextId(UUID.valueOf(context));

	// Save the work item
	HashSet additionalParams = new HashSet();
	additionalParams.add("WORK_ITEM_CONTEXT_CHANGE");

	IStatus status = wiServer.saveWorkItem3(fullItem, null, null,
			additionalParams);
	if (!status.isOK()) {
		String parameter[] = new String[1];
			parameter[0] = Integer.toString(fullItem.getId());

		String description = NLS.bind(
				"Failed to save work item ''{0}''.", parameter);
		IReportInfo info = collector.createInfo(
				"WORK_ITEM_CONTEXT_CHANGE"
				+ ": Error running the participant.",
				description);
		info.setSeverity(IProcessReport.ERROR);
		collector.addInfo(info);
		return;
	}

Please note, if this is performed in a participant/follow up action, it is important to send an additional save parameter like “WORK_ITEM_CONTEXT_CHANGE”. This protects the participants from causing a recursion ultimately crashing the server. The participant can protect itself to run again for the save it just performed by testing for the additional save parameter. If the parameter is detected, the participant knows it caused the save and should exit to prevent the recursion.

Please also note, that in some cases you might have a WorkItemWorkingCopy, instead of an IWorkItem. In these cases use a cast or the method getWorkItem() on the working copy to get the IWorkItem item to be able to set the context.

The examples above uses the string version of the UUID, however, it would have been possible to get the UUID directly from the IItem.

Getting the WorkItem Access Context

It is possible to get the access context of a work item, if the work item is accessible for read, by using IWorkItem.getContextId(). The resulting UUID can be used to get the related item.

Summary

This post explains how to set read access control for work items. The next post will explain the details around setting access control for SCM versionables.

Manage Access Control Permissions for Work Items and Versionables


A customer requires very fine grained access control to work items and versionable objects such as files in the SCM system. In addition the customer had the requirement to be able to set attributes on elements in the SCM system.

I knew that it can be set for work items and kind of how to because I had briefly looked at it in the A RTC WorkItem Command Line Version 3.0. But I was not completely sure about the rules and how to access the SCM part and the attributes in SCM.

Since the customer needed this urgently I looked at what the rules are and what can be done.

The content is way too big for just one post, so this is going to be a series. The first post explains the rules around access control permissions and some basic API’s around finding the objects that can be used to provide the access control context.

Related posts

The posts in this series are:

  1. Manage Access Control Permissions for Work Items and Versionables – this post
  2. Setting Access Control Permissions for Work Items
  3. Setting Access Control Permissions for SCM Versionables
  4. Setting Attributes for SCM Versionables

Also see

Controlling access to source control artifacts in Rational Team Concert

License and Download

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. Enjoy!

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

Compatibility

This code has been used with RTC 5.0.2 and is prepared to be used with RTC 6.0.x with no changes and it is pretty safe to assume, that the code will work with newer versions of RTC.

The code in this post uses common libraries/services that are available in the Plain Java Client, Eclipse client and Jazz Eclipse server API. If client or server API is used, this is stated.

Problem Description

The first question was, if it was possible to set the permission to access

  1. Work Items
  2. Items in the SCM system such as versionables

The second question was, if it was possible to set the attributes introduced in RTC 5.0 that can be specified and set using the API. The documentation only mentioned the SCM command line.

The third question was, how team areas and access groups can be created automatically.

The forth question was, what exactly the rules are:

  1. Can this be done in the server?
  2. Can this be done in the client?
  3. Can this be done in a command line tool or from another application?
  4. What permissions are required and what are the rules in the different contexts?

Solution Approach

The approach that we chose was to try to implement a minimal demonstrator to demonstrate the capabilities and prove them as a proof of concept. this demonstrator was also intended to serve as a platform to find the rules.

Learning

Here is what we learned creating the demonstrator.

The Rules – Work Items

Work Items have an attribute called “Restricted Access” that can be used to control access to them. There are basically the following modes available for work items that can be used.

  1. Access control using access control of the project area
  2. Restricted access by category
  3. Restricted access by setting the restricted access attribute

Option 1: Only the users that have access to the context set for the project area can access these items. This can be set to everyone basically exposing the work items to everyone. Other options a re project area membership and access control list etc. The rules here are very clear.

Option 2: It is possible to set RTC in a project area to automatically determine access permissions based on the category (filed against) of the work item. This sets the restricted access to the project area or team area associated to the category. Only members of the team area (and members of sub team areas of the team area associated to the category) have access to the work items which are restricted by category. It is worth noting, that a user being associated to the project area can not look into the work items filed against teams, if these limit access, it is quite the opposite.

Option 3: Use a special editor presentation to set the restricted access attribute manually to a project area or an access group. this could potentially be supported by automation.

The most important learning for option 3 is, that it is only possible to set the restricted access attribute to a project area or an access group this way. It is not possible to set the access context to a team area. If the context is set to a team area, it automatically picks up the containing project area. This also holds for automation.

See this help topic on how to set up the work items to allow setting the restricted access.

The Rules – Versionables

Prior to RTC 6.0.1, which has just been released, it is only possible to set read access control to the default (which is controlled by the component) project areas, team areas (called process areas if the distinction is unimportant) and a single user.

If access control is set to a team area, any member of that team area and its sub team areas have access. Note that this is different from option 2 for work items. If access control is set to a project area, any member who has access to the project area has access to the item.

RTC 6.0.1 introduces the capability to set access control to an access group. Only members of this access group have access to the item. This seems to be the best option by far, especially since access groups can contain project areas and team areas which adds their members to the access group.

Please note, that access control applies to the whole item and its history.

JazzAdmin Repository Role

Users with the JazzAdmin repository role have access to any work item or SCM controlled item, regardless of the access control setting. Users with this role (and sufficient licenses) can access all data.

Project Area Administrators

Administrators of a team or project area and don’t have the JazzAdmin repository role can not access items that have access control set to a context they don’t have access to.

General Rules

Users can only set the access context of work items or items in the SCM system to an access context that preserves their access permission. It is not possible to find or select an access group, process area the user has no access to/is member of or a different user and set the access context to it. This would remove the read permissions and is prevented.

Users with the JazzAdmin role and sufficient licenses however can do this, because they don’t loose read access.

General Rules For Automation

All automation can only perform the operations that are permitted to the user that is used to run the automation. This especially means that automation running in the context of a user with JazzAdmin repository role and the required licenses can perform all operations. The user context available in an automation depends on the automation.

  1. Follow up actions (participants) run in the context of the user that is performing the operation on the client or the server. They are especially not elevated run in a JazzAdmin repository role, if the user that performs the operation does not have this role. It is also not possible to use a different users or services to elevate the permissions.
  2. Preconditions (advisors) must not change the triggering elements. Otherwise the same rules apply as described in follow up actions.
  3. Asynchronous tasks or services in RTC are run with JazzAdmin permissions and can use the IImpersonationService to change the operation to a different user context.
  4. Plain Java Client Library or other client based automation run in the context of the user that was used to log into the system.

In the context of this post, 1 means, that it is not possible to set access control in such an operation that would remove read access from the user that performs the operation. If you require rules where this could be necessary, it would be necessary to run in a administrative context such as an Asynchronous task or in a client application with JazzAdmin repository role.

Finding ProcessAreas, Access Groups and Contributors

How to find the access context information needed using the Java API?

Finding ProcessAreas

There are many ways to find ProcessAreas (project areas and team areas) using the API, the simplest one assumes an java.net.URI constructed from a string based name path of the process area separated by ‘/’.

Lets assume a project area named TestProject1 and a team area within the project are named TestTeam1 and a sub team area underneath TestTeam1 named TestSubTeam1.
The URI for the project area could be constructed from the string “TestProject1”. The URI for TestTeam1 could be constructed from the string “TestProject1/TestTeam1”. Similar the URI for TestSubTeam1 could be constructed from the string “TestProject1/TestTeam1/TestSubTeam1”.

Process area names could contain spaces. Spaces are not allowed in URI’s, therefore they need to be replaced by the encoding character string “%20”.

To construct an java.net.URI from a string value use the following code.

String namePath = "TestProject1/TestTeam1/TestSubTeam1";
URI anURI = URI.create(URI.create(namePath.replaceAll(" ", "%20")))

The service to find the process area by its URI is provided by the interface IAuditableCommon. This interface class, as its name implies, is a common interface that is available in the RTC Plain Java API, as well as the RTC Eclipse client and Eclipse server SDK. This allows to use this API call in any possible context, either by

IAuditableCommon auditableCommon = (IAuditableCommon) teamRepository
	.getClientLibrary(IAuditableCommon.class);

in Plain Java and client SDK based code, or using

IAuditableCommon auditableCommon = getService(IAuditableCommon.class);

in the server extensions that all extend AbstractService (or in case of SCM server extensions AbstractScmService, which extends AbstractService).

For code that already retrieved the common Service IWorkItemCommon, for example to find work items, IAuditableCommon is available using the call

IAuditableCommon auditableCommon = workItemCommon.getAuditableCommon();

So the following code would retrieve the access context for the team area “/TestTeam1/TestSubTeam1” nested in the team area “TestTeam1” in the project area “TestProject1”

String namePath = "TestProject1/TestTeam1/TestSubTeam1";
IProcessArea area = auditableCommon.findProcessAreaByURI(URI.create(namePath.replaceAll(" ", "%20")), null, monitor);
UUID context = area.getItemId();

Note, for work items only a project area is a valid context. Setting the context above will result in the project area containing the process area to be set. Alternatively use

UUID context = area.getProjectArea().getItemId();

for work items and document the fact to save the time to figure it out the hard way during testing (like I did).

Finding the Public Access Context

Work items can also be set to public read access. The constant com.ibm.team.repository.common.IContext.PUBLIC is available for that.

UUID publicContext = IContext.PUBLIC.getUuidValue();

provides with the public access context.

Finding Access Groups

Access Groups are managed in the administration pages of the RTC application. It is possible to create, modify and delete access groups. The image below shows this section.

Access Group ManagementAccess Groups are also accessible using the IAuditableCommon common interface that was already used in the section above.

To get all access groups use this code:

IAccessGroup[] groups;
groups = auditableCommon.getAccessGroups(null, Integer.MAX_VALUE,
	monitor);

This returns all access groups up to a maximal number that is passed to the method.
It is possible to pass a filter to select only access groups with specific name pattern like below.

IAccessGroup[] groups;
groups = auditableCommon.getAccessGroups("My*", Integer.MAX_VALUE,
	monitor);

The access groups can be iterated like below to compare the name or do something similar.

for (IAccessGroup group : groups) {
// Compare the value to the access group name.
	if (group.getName().equalsIgnoreCase(value)) {
		return group;
	}
}

Similar to the process areas above, there is also a public access group which can be obtained like this:

return auditableCommon.getAccessGroupForGroupContextId(
	IContext.PUBLIC, monitor);

Finding Contributors

Finding contributors is different in the client and the server. There is no common API available.
In the client API the IContributorManager can be used for example using the following methods.

IContributorManager contributorManager= teamRepository.contributorManager();
// Find a user by the users ID returns a handle
IContributorHandle contributorHandle = contributorManager
	.fetchContributorByUserId(attributeValue, monitor);

// Get all users, returns a list of full IContributor items
List contributors = contManager.fetchAllContributors(monitor);

In the server API the IContributorService is available and provides the following method.

IContributorService contributorService = getService(IContributorService.class);
contributorService.fetchContributorByUserId(userId);

I was not able to locate a way to search for all users like in the client API. The server API usually gets a user passed and there is no need to find all users.

Summary

This post explains the rules around read access control for work items and RTC SCM versionables and some basic API’s around finding the objects that can be used to provide the access control context. The next post will explain the details around setting access control for work items.

 

A RTC WorkItem Command Line Version 3.0


I was interested in how complex it might be to export work items and import them again. So I looked into this and enhanced the work item command line (WCL) to support this.

I found it quite challenging to develop this. There are a lot of things to consider, so I drove this to a point that was sufficient for my purposes. The tool is used by my team to import work items from a CSV file we receive every now and then.

I implemented another export/import mode with some more capabilities and it works in tests, but is by no means production ready. The amount of necessary tests and test automation to make this reliable, is just overwhelming. So this is not thoroughly tested.

So be careful if using these commands, and do a good amount of testing before actually using this. The problem is, there are so many possible use cases and dependencies that it is very hard to develop this kind of capability and to test it.

One special case is importing/creating links. Some links have constraints i.e. parent and especially child links. A work item can not have multiple parents. So setting child links can cause the save to fail if the new child has already a different parent.

Solution Overview

The work item command line WCL now supports two new commands

  • exportworkitems
  • importworkitems

To export work items to a CSV file and import work items from a CSV file.

I chose to use a CSV file, because RTC itself can export and import that format already. It would be ideal if export and import from XML would be supported as well, but this would require a substantial effort to abstract the export and import operations to be able to use a strategy (or some other useful pattern to support abstraction).

No Support or Maintenance

This is provided as-is with no support or guarantee. This is not a tool that is officially supported by IBM or any other organization.

Please note, that I have very little time to do this and testing is always lacking. So take the code published here with a grain of salt. On the positive side, you have the code, can debug and enhance it.

Compatibility

This code has been used with RTC 4.x and 5.x with no changes and it is pretty safe to assume, that the code will work with newer versions of RTC. The code requires two external libraries that need to be downloaded and installed separately.

License and Download

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. Enjoy!

Update: Added switch to change the export and import formats for dates, see details below. I also added a switch to suppress attribute not found errors and other frequent errors during export.

Update: Fixed duration set problem. Version updated to 3.2

You can download the latest version from this post The RTC Work Item Command Line On Bluemix. The older version 3.0 can be found here:

Please note, there might be restrictions to access Dropbox and therefore the code in your company or download location.

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 following code in this environment and get your own automation or extension working. The code linked from this post contains Client API.

Setup and Usage

For the general setup follow the description in A RTC WorkItem Command Line Version 2 and look at the additional setup steps below.

For usage follow the description in A RTC WorkItem Command Line Version 2 and in A RTC WorkItem Command Line Version 2.1. Check the README.txt which is included in the downloads.

Export Work Items

The export work items command has the syntax

-exportworkitems {Switch} repository=”value” user=”value” password=”value” projectArea=”value” query=”value” exportFile=”value”  [columns=value] [encoding=value] [delimiter=value] [querysource=value]

Required Parameters are

  • repository=”value” – the repository URI, for example repository=”https://clm.example.com:9443/ccm
  • user=”value” – The user ID of the user executing the command, for example user=”ralph”
  • password=”value” – the password of the user, for example password=”password”
  • projectArea=”value” –  The project area to export items from, for example projectArea=”JKE Banking (Change Management)”
  • query=”value” – the name of the query to use, for example query=”All WorkItems”
  • exportFile=”value” – The path of the export file, for example exportFile=”C:\aaTemp\Export\Test.csv”; the folder that contains the export file must exist

Optional Parameters are

  • columns=value – The names or ID’s of the work item attributes to export; example columns=”Type,Id,Planned For,Filed Against,Description,Found In”; To specify the colums it is possible to use the name or the ID of the attribute, the switch headerIDs specifies the output format to use the ID instead of the name in the output; It is possible to use the values from an RTC Eclipse client export
  • encoding=value – The encoding; default encoding=”UTF_16LE”; options see available charset names; if the encoding is chosen different for export and import, the values will not be recognizable
  • delimiter=value – The delimiter to be used between the columns. Default is comma delimiter=”,”
  • querysource=value – If the parameter is omitted the command searches a personal query with the given name; if the value is provided a query shared by the process area is searched, a complete path from the project area to the sharing process area must be provided, for example querysource=”JKE Banking(Change Management),JKE Banking(Change Management)/Business Recovery Matters”
  • timestampFormat=value – To specify the time stamp format to be used; default “MMM d, yyyy hh:mm a”;  see SimpleDateFormat for the supported pattern

Available switches are:

  • /ignoreErrors – Ignore minor errors in mapping and value lookup
  • /asrtceclipse – Export in a format compatible to the RTC CSV export and import; if the switch is not provided, the data is exported in a format that is compatible with the syntax used by the work item command line WCL to identify elements; see A RTC WorkItem Command Line Version 2 for the supported value representations
  • /headerIDs – Export header values as attribute IDs and not as attribute names
  • /suppressAttributeExceptions – Suppresses exceptions thrown for attributes that are not available on the work item type of for attribute types that are not yet implemented

Example

-exportworkitems /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph projectArea="JKE Banking (Change Management)" exportFile="C:\aaTemp\Export\Test.csv" query="All" columns="workItemType,summary,Attachments"

Import Work Items

The import work items command has the syntax

-importworkitems{Switch} repository=”value” user=”value” password=”value” projectArea=”value” query=”value” importFile=”value”  [columns=value] [encoding=value] [delimiter=value] [querysource=value]

Required Parameters are

  • repository=”value” – the repository URI, for example repository=”https://clm.example.com:9443/ccm
  • user=”value” – The user ID of the user executing the command, for example user=”ralph”
  • password=”value” – the password of the user, for example password=”password”
  • projectArea=”value” –  The project area to export items from, for example projectArea=”JKE Banking (Change Management)”
  • importFile=”value” – The path of the import file, for example importFile=”C:\aaTemp\Export\Test.csv”

Optional Parameters are

  • mappingFile=”value” – A RTC work item import mapping file, for example mappingFile=”C:\temp\mapping.xml”; the file must be generated by RTC and customized to match the value mapping
  • encoding=value – The encoding; default encoding=”UTF_16LE”; options see available charset names; if the encoding is chosen different for export and import, the values will not be recognizable
  • delimiter=value – The delimiter to be used between the columns. Default is comma delimiter=”,”
  • timestampFormat=value – To specify the time stamp format to be used; default “MMM d, yyyy hh:mm a”;  see SimpleDateFormat for the supported pattern

Available switches are:

  • /ignoreErrors – Ignore minor errors in mapping and value lookup
  •  /importmultipass – Import the work items from the CSV file in a first iteration and build up a mapping for the ID’s provided in the import file and the actual ID’s created and recreate the work item links between the new work items based on that mapping in a second pass; the old work item ID for a work item has to be provided in a special column with header name com.ibm.js.oldid
  • /forcelinkcreation – if no target work item can be found in the map, use the given ID to create the link
  • /importdebug – Print more information during import attempts to help with finding issues
  • /enforceSizeLimits – Attributes such as description and medium strings have size limits, if this switch is set, the importer tries to clip content to avoid exceptions due to the size limits

Example

-importworkitems  /enforceSizeLimits  /importmultipass  /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph projectArea="ImportTest1" importFile=""c:\aaTemp\ExportImport\TestExportAll.csv"

RTC Eclipse Compatible Export Mode

In mode asrtceclipse, all data is exported the way RTC would export them in Eclipse. This means that certain information for example links, team areas, iterations, attachments and other data is exported in a way that makes it hard to map to data in the repository.

The import command does its best to map based on names, but for complex hierarchical information such as iterations and team areas, there is currently no search implemented that will find the object successfully. It would be possible to implement such methods, with some effort.

Example: An iteration is part of a timeline. The timeline is needed to find the iteration within. If there is no information about the timeline, it would be required to iterate all timelines with a good chance of mismatch.

The import command will try to find things by name and ID, with the limitations above.

If the work item ID attribute is provided as a column the importer will try to find the work item and update it during the import.

Default Export Mode

In the default export mode, the RTC Work Item Command line export command exports the data in greater detail, which makes it easier for the importer to identify the item.

Attachments

In default mode, the attachment is exported as a file relative to the location of the generated csv file. The attachment is downloaded to a location ./attachments//. So for each exported work item with attachments, a separate folder is created. The attachments are stored in that folder and the export information in the csv file is created compatible to the WCL parameter format to allow later import of the attachments, including upload and applying the additional information.

Other Complex Items and Links

Other complex items such as iterations and team areas are also exported with a lot more details. An iteration is being exported as path, including the timeline and parent iterations. A team area is also exported as path containing the Project are name and parent team area names

Multi-Pass Import

Importing work items and recreating the link relationships between them is problematic, because while importing the work items the link target may not yet exist. To be able to import a set of work items and then recreate the linkage, it is necessary to do the import and then map the ID of the old work item to the ID of the new work item.

When using the RTC CSV importer in the Eclipse client, existing work items are provided with a # in front of the work item ID. To do an import and then recreate the links between the new work items (and not to the old ones in the import), a user would have to run the import without the links, then replace the work item ID’s in the import file by the new work item ID’s and update the work items with a second import. This is very manual and error prone.

The switch importmultipass  enables an import mode, where the WCL tries to create the links between the imported work items, rather to the old ones. It imports the work items in two passes. It creates the work items in the first pass and ignores the link creation. In the second pass it tries to create the links. For links between work items WCL tries to find the work items that were created during the import and tries to match the links to the new work items, where possible.

Note: Only links between work items are handled this way. Links to objects other than work items are recreated using the values provided in the import file.

To be able to do this, the import file has to provide the old work item ID of the work items that are imported. The import requires a special ID for the columns containing the old ID’s. The column header for this column has to be specified with com.ibm.js.oldid.

The import file below has been created using an export that included the ID of the work item in the export. The old column header for example ID of the column has been replaced by com.ibm.js.oldid. The work item links show the ID’s of the linked work items with their old ID’s.

Import Work Items With LinksThe import works as follows.

The WCL runs the first pass and imports the work items. It stores the mapping between the original work item ID from the column com.ibm.js.oldid and the ID of the newly created work item in a map. Links are not created in this pass.

In the second pass WCL reads the import file again and only handles the columns that represent links. It detects if the link target represents a work item. If not, it tries to recreate the link as it is. If the link is a work item link, WCL tries to calculate if a new work item was created for the target using the map. If the work item was imported and a new ID is available, the new work item ID is used to create the link.

If the ID of the link target can not be found in the mapping, WCL can either ignore the link or it can try to create the link to the original work item. WCL supports these two modes. By default, the link is not created. If the switch forcelinkcreation is specified, the original value of the target work item ID is used as target for the link, if no mapping to a newly imported item was found.

Creating links is not trivial. One special case is importing/creating links. Some links have constraints i.e. parent and especially child links. A work item can not have multiple parents. So setting child links can cause the save to fail if the new child has already a different parent. This can create issues in import scenarios, especially if an export from the same repository is imported and the import causes child links to be created that have already another parent. In this case the import will fail with an error.

Limitations

Approvals and comments are imported into one comment. The effort to recreate approvals is just too big and I can’t see the added value.

Special Notes On Setup

For the general setup follow the description in A RTC WorkItem Command Line Version 2 and look at the additional setup steps below.

The export and the import commands of WCL need two libraries that are not shipped the downloads.

If you use the packaged WCL and want to use the export/import capability follow the steps below to add the required libraries to the folder lib in the folder lib in the WCL folder.

If you use the Eclipse project for WCL and want to use the export/import capability follow the steps below to add the required libraries to the folder lib in the Eclipse project com.ibm.js.team.workitem.commandline.

The export and the import commands of WCL use the Open CSV Library. I had issues with the newer versions of Open CSV that I could not resolve, so this code assumes the version 2.3. Download the version 2.3 from here. Uncompress and untar the the file opencsv-2.3-src-with-libs.tar.gz you downloaded. Look for the folder opencsv-2.3\deploy\ copy the JAR file opencsv-2.3.jar and put it into the lib folder of your version of WCL.

The import commands of WCL can only provide the capability to use a mapping file by using a JAR file that only ships with the RTC Eclipse client and the SDK. The classes used for the mapping file capability are located in the library com.ibm.team.workitem.rcp.core.  Open the Install location of the RTC Eclipse client and search for com.ibm.team.workitem.rcp.core*. You should find a file names similar to this one: com.ibm.team.workitem.rcp.core_3.1.900.v20141010_0124.jar. The version numbers at the end could be different. Copy the JAR file into the into the lib folder of your version of WCL.

The packaged version should look like below.

Deployed Packaged WCLIf you have imported the Eclipse project for WCL open Configure Build Path and create a user library named openCSV and add the Open CSV library opencsv-2.3.jar. Create a user library named rtcmapping and add the com.ibm.team.workitem.rcp.core library you just copied to it.

Your Eclipse Project should now look like below.

Eclipse Project and LibrariesCode Changes

During the work on import and export, the code structure was left untouched. Some classes were added to be able to handle the column header and some additional mapping of id’s and names i.e. for link types. In addition some of the code that was piling up in the WorkItemUpdateHelper (formerly known as WorkItemHelper) was moved to utility classes. this makes it also easier to look for useful API in case you are interested in how things work in the RTC API. See the scree shot below.

Code StuctureSummary

This WorkItem Command Line should allow for most of the automation needs when creating work items. In addition it is a nice resource for the RTC work Item API.

As always, I hope the post is an inspiration and helps someone out there to save some time. If you are just starting to explore extending RTC, please have a look at the hints in the other posts in this blog on how to get started.

A Custom Condition to Make Attributes Required or Read-Only by Role


Recently, a customer wanted to make attributes required and read only in a way the built in operational behavior does not support. So I tried to find out if there would be a way to achieve this by an extension. After struggling for some time, I finally found a way and want to share this with the community.

Update: see A Custom Condition to Make Attributes Required or Read-Only by Role Version 2 for some more tricks and information.

The Requirement

The requirement was,

  1. A user can have multiple roles
  2. Attributes are required or read only for a role in a certain state
  3. For a user all roles shall be evaluated and the attribute be required or read only if any of the roles the user has, specifies so

So the intended behavior was more like permissions work in RTC, accumulate over all roles.

The built in RTC mechanisms for required and read only attributes are based on operational behavior. Built in advisors can be configured to provide the information and behavior.

RTC Operation BehaviorYou can configure operational behavior for each role, in this example it is for the default role Everyone. However, the operational behavior in RTC does only look for the first configured operational behavior for the operation that is configured for a role the user has, as described in Process behavior lookup in Rational Team Concert 2.0. This is which is still valid for later versions of RTC.

A user has one (every user has the Everyone role) or more roles configured if he is member of a project or team area. The roles have an order, from top to bottom. The first configured operational behavior for the first role in that order that is found in the context of an operation will be executed. Only that first found operational behavior will be executed.

The idea behind this concept is, that it is possible to overwrite the operational behavior by having specific roles in a specific order. It is, for example, possible to have configured that users with the role Everyone need to provide several required attributes to save a work item and several attributes can be read only. But the Team Lead may have a lot less required attributes and no read only attributes, because the operational behavior is specified in a different way for the Team Lead role.

This also means, if the check box “Preconditions and follow-up actions are configured for this operation” is checked for a role and no preconditions are configured, RTC will do nothing, if the user has that role, even if a lot of preconditions are configured for the role Everyone. RTC will find the specification for that role and as there is nothing configured assume there is no operation behavior needed for the role.

It also means, if operational behavior is specified for all roles, only the operation behavior of the primary role the user has in the context, is executed.

The customer wanted a behavior that was different. Assume role1 and role2 exist. A work item attribute is specified to be read only for role1 but not read only for role2. The attribute shall be read only for all users that have role2 assigned, regardless of the order of the roles.

This is, again, more like permissions as described in Process permissions lookup in Rational Team Concert 2.0 where if one role a user has, has the permission, the user has this permission.

How Does the RTC Code Work?

I looked at how this is implemented in RTC by looking into the SDK in the hope to find a way to extend it somehow. It is pretty easy to find the code of the advisors/preconditions shipped with RTC.  It took me a while to figure out what is going on in the code.

One thing that had bothered me for a while became understandable in the process: Operation behavior is run after the save button is pressed. How can operational behavior have any impact on the UI before the button is pressed? How can the UI show attributes as read-only or as required before the operation is performed?

Well, it turns out, that the built in advisors have a static part, that reads the configuration. The UI knows the built in advisors and calls this part to get the configuration data in order to require attributes or make them read only. Mystery solved.

This, of course means there is no way to create your own advisor/precondition to extend RTC to work with different rules and the UI showing behaving like with the built in advisors. The UI does not know that it needs to get the custom configuration and that’s it. It is still possible to prevent a save, but the UI won’t provide any information up front.

At this point I was very close to giving up. The issue had passed a lot of smart people’s desks at that time, including mine – 5 times at least. Why should I find a solution if no one else had the slightest idea?

The Solution

Well, when looking at the code, I realized some other built in operation behavior was wired up there as well. Code for the advisors to control required and read only attributes for conditions.

Advisors for ConditionsThis turned out to be the approach that allowed to implement the requirement. Create a custom condition that allows to configure the condition advisors in a way to provide the functionality as requested.

One important issue that became apparent in the process of creating this solution is that conditions are different from all the other attribute customization providers. This almost led to me failing in finding a solution to implement this.

Conditions need to be instantiated like all the other providers but

  1. Condition instances are not configured at the attribute
  2. Conditions are only configured in the aforementioned preconditions
  3. Conditions don’t get the information about the attribute they are configured for

My initial idea was to somehow configure the condition’s additional script parameters in the process XML with information about attribute ID’s, for which roles in which states the condition returns false. This does not work. Instead the condition gets information for which state Id for which work item category, which roles are relevant. This way one condition can handle all cases as we will see in the following sections.

Solution Summary

The condition, lets call it User Role for Type and State Condition is a Java based extension for the Eclipse client and the RTC server. It allows configuring roles mapped to work item type categories and states.
The condition checks if the current work items category and state has roles configured. If roles are found, and the user has any of the roles, the condition returns true, false otherwise.
This condition can be configured and be used to make work item attributes read-only or required for a work item type category in a specific state based on the roles a user has in this context.

License and Download

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. Enjoy!

The code can be downloaded from DropBox here. Please note, there might be restrictions to access DropBox and the code in your company or download location.

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.

In this context, please also consider to at least read through the Process Enactment Workshop for the Rational solution for Collaborative Lifecycle Management lab 4 and lab 5 to understand how attribute customization works.

Import The Code

Use the Eclipse importer to import existing projects into the workspace from an archive file.

Import Step 1

Browse for the archive.

Import Step 2

Select all projects and press finish. The code now shws up in your Eclipse workspace.

How The Code Works

The code comes in four projects.

Project Structure

com.ibm.js.team.workitem.attribute.user.role.condition.providers: The main project for the extension. It contains all the code that is needed and defines the plugin.xml.

com.ibm.js.team.workitem.attribute.user.role.condition.providers.feature: The feature project needed to be able to deploy the code.

com.ibm.js.team.workitem.attribute.user.role.condition.providers.updatesite: The update site needed to generate the code for deployment. This project output is also used to deploy the extension in RTC Eclipse clients.

com.ibm.js.team.workitem.attribute.user.role.condition.providers.serverdeploy: A special project to help deploying the code on RTC servers.

The structure of the main project looks like this.

Condition Code StructureThe source code is provided in four classes. The majority of the code is implemented by the class AbstractUserRoleTypeAndStateConditionProvider. It implements all of the behavior needed.

AbstractUserRoleTypeAndStateConditionProviderThe only piece missing in the code is the part that provides the process areas (project and team areas) used to look for the roles the user has. It is possible to use different approaches to configure this in RTC and based on how this is configured in RTC there are different possible approaches you want to use to get that information. So this is left abstract to be implemented in an extending class.

Possible strategies are:

  1. Look for the roles a user has in the team area that owns the work item
  2. Look for the roles a user has in the project area
  3. Look for all the roles a user has across the hierarchy of the area that owns the work item up to the project area

There might be other strategies, dependent on the context this is used in. It is possible to extend the abstract class and to provide the process areas to look into.

The class

  • ProcessAreaHierarchyUserRoleTypeAndStateConditionProvider implements strategy 3
  • ProcessAreaUserRoleTypeAndStateConditionProvider implements strategy 1
  • ProjectAreaUserRoleTypeAndStateConditionProvider implements strategy 2

Lets have a quick look at the code provided in the abstract class.it implements the method matches required by the Interface ICondition that is needed to be implemented for a condition.

/* (non-Javadoc)
 * @see com.ibm.team.workitem.common.internal.attributeValueProviders.ICondition#matches(com.ibm.team.workitem.common.model.IWorkItem, com.ibm.team.workitem.common.IWorkItemCommon, com.ibm.team.workitem.common.internal.attributeValueProviders.IConfiguration, org.eclipse.core.runtime.IProgressMonitor)
 */
@Override
public boolean matches(IWorkItem workItem, IWorkItemCommon workItemCommon,
		IConfiguration configuration, IProgressMonitor monitor)
		throws TeamRepositoryException {

	// Get the work item type category of the work item to determine if this
	// condition is configured for it
	IWorkItemType wiType = workItemCommon.findWorkItemType(
			workItem.getProjectArea(), workItem.getWorkItemType(), monitor);
	String typeCategory = wiType.getCategory();

	// Get the workflow action
	String actionId = configuration.getProviderContext()
			.getWorkflowAction();
	// Get the work item state. 
	// The state could be the current one, or it could be determined 
	// by the workflow action that is currently selected
	String wiState = findTargetStateId(workItem, actionId, workItemCommon,
			monitor);
	// Find the roles that are configured for the work item type (by type
	// category) for the work item state
	Set roles = getRoleConfiguration(typeCategory, wiState,
			configuration);
	if (null == roles) {
		// No Roles found, we can exit.
		return false;
	}

	// Get the roles the contributor has in this context
	Collection contributorRoles = getContributorRoles(workItem,
			workItemCommon, monitor);
	// Return true, if the contributor has any of the configured roles
	return hasMatchingRole(roles, contributorRoles);
}

This method first looks up the work item type and from that the work item type category. We use the type category, because all work item types of the same category have the same attributes and workflow. It would be possible to use the type directly, if needed.

Then it looks up the current workflow action from the provider context. As described in the AttributeCustomization wiki entry, conditions get the currently selected workflow action. The condition needs this to determine if a state change is about to happen and to get the roles configured for that state and not the current one. The state that is relevant for this operation is looked up.

The method then uses the work item type category, the relevant state and the configuration to determine the roles that are configured for this context. If there are no roles valid for this context the condition can end and return false.

If there are roles configured for this situation, the method gets the roles of the user trying to perform the operation.

The final check is, if the current user has any of the roles configured for this context.

Lets look at how finding the state of the work item works in findTargetStateId(). The code can be found in the SDK in the context of the preconditions.

/**
 * Find the target state of the work item for the condition. 
 * The target state is the current state if there is no workflow action selected
 * If there is a workflow action selected, the target state is the state
 * the action results in.
 * 
 * @param workItem
 * @param actionId
 * @param workItemCommon
 * @param monitor
 * @return the state ID (or null if there is no identifiable state) 
 * @throws TeamRepositoryException
 */
private static String findTargetStateId(IWorkItem workItem,
		String actionId, IWorkItemCommon workItemCommon,
		IProgressMonitor monitor) throws TeamRepositoryException {
	Identifier state = workItem.getState2();
	IWorkflowInfo wfInfo = workItemCommon.findWorkflowInfo(workItem,
			monitor);
	if (state == null && actionId == null && wfInfo != null) {
		actionId = wfInfo.getStartActionId() == null ? null : wfInfo
				.getStartActionId().getStringIdentifier();
	}
	if (wfInfo != null && state != null) {
		if (!Arrays.asList(wfInfo.getAllStateIds()).contains(state)) {
			actionId = wfInfo.getStartActionId() == null ? null : wfInfo
					.getStartActionId().getStringIdentifier();
		}
	}
	if (actionId != null && wfInfo != null) {
		state = wfInfo.getActionResultState(Identifier.create(
				IWorkflowAction.class, actionId));
		if (state == null) {
			actionId = wfInfo.getStartActionId() == null ? null : wfInfo
					.getStartActionId().getStringIdentifier();
			if (actionId != null) {
				state = wfInfo.getActionResultState(Identifier.create(
						IWorkflowAction.class, actionId));
			}
		}
	}
	// This is code that addresses a change in the process,
	// where the state of a work item can have only a number
	// Make sure the number is modified to reflect the state ID
	if (state != null) {
		String stateId = state.getStringIdentifier();
		try {
			Integer.parseInt(stateId);
			stateId = "s" + stateId;//$NON-NLS-1$
		} catch (NumberFormatException e) {
		}
		return stateId;
	}
	return null;
}

The code gets the work item state and the workflow information first. If there is no state and no action, then the work item is new and the action is the start action.

Then it looks at the case where there is a state and a work flow action, if it can’t find the current state in the workflow, there was a type change and the action is the start action (or none).

With the action identified, it calculates the target state. If there is none, the current state remains.

Finally there is a handling of the state ID’s. In some cases, for historical reasons, only a number is returned and not a sate id, The last bit of the code makes a proper state ID from the number, if needed.

The correct target state of the work item is returned at the end.

Another interesting part is to get the configuration for the roles from the process configuration done in getRoleConfiguration().

/**
 * Get the roles that are configured for the work item category and current target state
 * 
 * @param typeCategory
 * @param wiState
 * @param configuration
 * @return returns a set of roles that are configured for the 
 * work item category and state, or null, if there is not matching configuration 
 */
private Set getRoleConfiguration(String typeCategory,
		String wiState, IConfiguration configuration) {
	List workflowConfigurations = configuration
			.getChildren(CONFIGURATION_ELEMENT_WORKFLOW_PROPERTIES);
	if (null != workflowConfigurations) { // We got a configuration
		// For all configuration elements 
		for (IConfiguration workflowConfiguration : workflowConfigurations) {
			// Get the workitem state for this configuration element
			String foundStateID = workflowConfiguration
					.getString(CONFIGURATION_WORKFLOW_PROPERTY_ATTRIBUTE_STATE_ID);
			// Get the work item category for this configuration element
			String foundWorkflowCategory = workflowConfiguration
					.getString(CONFIGURATION_WORKFLOW_PROPERTY_ATTRIBUTE_WORK_ITEM_TYPE_CATEGORY);

			// If the configuration element applies to the current work item
			// state and category, get the roles that are configured
			if (foundStateID != null && foundWorkflowCategory != null
					&& foundWorkflowCategory.equals(typeCategory)
					&& foundStateID.equals(wiState)) {
				return getRoles(workflowConfiguration);
			}
		}
	}
	return null;
}

The conditions can be configured in the process configuration source as described here. The code reads the configuration data in the process.xml. It tries to find a configuration for the work item category and the state in the configuration. If one is found, it gets all roles specified and returns them.

The method getRoles() looks as below:

/**
 * Get the roles configured for this configuration element
 * 
 * @param workflowConfiguration
 * @return the roles found
 */
private Set getRoles(IConfiguration workflowConfiguration) {
	Set roles = new HashSet();
	List roleConfigurations = workflowConfiguration
			.getChildren(CONFIGURATION_ELEMENT_ROLE);
	for (IConfiguration roleConfiguration : roleConfigurations) {
		roles.add(roleConfiguration.getString(CONFIGURATION_ATTRIBUTE_ROLE_ID));
	}
	return roles;
}

It basically also reads the next level in the process configuration XML to get the configured role ID’s.

When designing the condition the following structure for the configuration was chosen.

Configuration SyntaxBasically provide the workflowProperties for the workitem type category and the state. Underneath provide the ID’s for the roles the condition should trigger.

As an example of the configuration in the process XML:

ConfigurationThe last step is to get the roles of the user that tries to perform the operation. This is done in getContributorRoles().

/**
 * Get the roles for a contributor
 * 
 * @param processArea
 * @param user
 * @param workItemCommon
 * @param monitor
 * @return
 * @throws TeamRepositoryException
 */
private Collection getContributorRoles(IWorkItem workItem,
		IWorkItemCommon workItemCommon, IProgressMonitor monitor)
		throws TeamRepositoryException {
	Collection roles = new HashSet();
	// Get Current User - we will check for the roles this user has
	IContributorHandle user = workItemCommon.getAuditableCommon().getUser();

	// Get the relevant process area(s) to look for the role
	Collection processAreas = getProcessAreas(workItem,
			workItemCommon, monitor);
		
	// Iterate the relevant process areas
	for (IProcessAreaHandle processAreaHandle : processAreas) {
		// Resolve the process area
		IAuditableCommonProcess auditableCommonProcess = workItemCommon
				.getAuditableCommon()
				.getProcess(processAreaHandle, monitor);
		IProcessArea processArea = (IProcessArea) workItemCommon
				.getAuditableCommon().resolveAuditable(processAreaHandle,
						ItemProfile.PROCESS_AREA_DEFAULT, monitor);
		// get the roles and add them to the list of roles the contributor has
		roles.addAll(auditableCommonProcess.getContributorRoles(user,
				processArea, monitor));
	}
	return roles;
}

The method gets the current user. Then it calls the abstract method getProcessAreas() to get the process areas to look for roles. It then iterates the process areas retrieved, gets the process and the roles of the user.

The method hasMatchingRole() just iterates the roles found and returns true, if the user has a role that is configured in the configuration for the given work item category and the state the work item has in this context.

/**
 * Check if the contributor has one of the configured roles.
 * 
 * @param roles
 * @param contributorRoles
 * @return true if the contributor has a role that is found in the configuration
 */
private boolean hasMatchingRole(Set roles,
		Collection contributorRoles) {
	for (IRole aRole : contributorRoles) {
		if (roles.contains(aRole.getId())) {
			// The user has a role that was relevant for this configuration
			return true;
		}
	}
	return false;
}

The classes that implement the AbstractUserRoleTypeAndStateConditionProvider basically have to implement which process areas to look at and to return them in getProcessAreas().

The version below is the most complex one, that iterates the whole hierarchy implemented in the class ProcessAreaHierarchyUserRoleTypeAndStateConditionProvider.

/***
 * 
 * Get the list of process areas to look up the roles for the contributor
 * Start with the process area a work item is filed against
 * and iterate the process area hierarchy up to the project area.
 * 
 * All found process areas are added to the search list.
 * 
 */
/*(non-Javadoc)
 * @see com.ibm.js.team.workitem.attribute.roletypestate.condition.providers.AbstractUserRoleTypeAndStateConditionProvider#getProcessAreas(com.ibm.team.workitem.common.model.IWorkItem, com.ibm.team.workitem.common.IWorkItemCommon, org.eclipse.core.runtime.IProgressMonitor)
 */
@Override
Collection getProcessAreas(IWorkItem workItem,
		IWorkItemCommon workItemCommon, IProgressMonitor monitor)
		throws TeamRepositoryException {
	// Get the project area
        HashSet processAreas = new HashSet();
        // Resolve with full data to get the hierarchy
	IProjectArea projectArea = (IProjectArea) workItemCommon
			.getAuditableCommon().resolveAuditable(workItem.getProjectArea(),
					ItemProfile.PROJECT_AREA_FULL, monitor);
		
	// Get the hierarchy to be able to find the process area parents
	ITeamAreaHierarchy hierarchy = projectArea.getTeamAreaHierarchy();

	// Start with the process area the work item is filed against
	IProcessAreaHandle processAreaHandle = workItemCommon.findProcessArea(workItem, monitor);
	do{
		// If this is a team area, add it and look for the parent area
		if (processAreaHandle instanceof ITeamAreaHandle) {
			processAreas.add(processAreaHandle);
			try {
				// Try to get the parent process area
				processAreaHandle = hierarchy.getParent((ITeamAreaHandle)processAreaHandle);
			} catch (TeamAreaHierarchyException e) {
				// this should not happen, if it does, stop the loop
				return processAreas;
			}
		} else if (processAreaHandle instanceof IProjectAreaHandle) {
			// If the area is the project area, we are done
			processAreas.add(processAreaHandle);
			return processAreas;
		}		
	} while (processAreaHandle!=null);	
	return processAreas;
}

It basically gets the project area of the work item and looks up the ITeamAreaHierarchy for it. Then it gets the process area that owns the work item. If that is a team area and not the project area, it adds the team area to the list and then tries the same with its patent process area. If the process area is a project area, it is added to the list and the method is done.

 The Plugin.XML

The plugin.XML basically defines the value providers that are available as well as the component for them.

Plugin.XMLThe condition providers are configured as shown below.

Condition Provider ConfigurationDeploying the Extension

Before deploying, the code has to be built. This is done in the project com.ibm.js.team.workitem.attribute.user.role.condition.providers.updatesite

Make sure the update site project is empty like below

Empty Update Site ProjectDelete any other files and folders visible besides the .project file and the site.xml, e.g. Jar-files and folders like plugins and features.

Open the site.xml and press Build All in the editor.

Build All Update SiteThe update site project now has new files and folders.

Update Site Ready to Deploy to ClientThese files will be used to deploy the extension to the server and later to deploy the extension on the RTC Eclipse client.

Deploy on the RTC CCM Server

There is a special project that was artificially created to help with deploying on the server. The project com.ibm.js.team.workitem.attribute.user.role.condition.providers.serverdeploy contains a folder structure that resembles the structure in the configuration folder of the server. The folder provision_profiles/ contains the provisioning file js_user_role_condition_provider.ini that contains the information needed to deploy the extension on the server. It also contains the reference to the folder site/js_user_role_condition_provider which is reflected in the project structure as well. This folder needs to contain the built features and plugins. By setting this up this way, it is relatively easy to successfully deploy the extension.

Server Deploy Project

After building, copy the folders features, plugins and the file site.xml from the project com.ibm.js.team.workitem.attribute.user.role.condition.providers.updatesite into the folder sites/js_user_role_condition_provider in the project com.ibm.js.team.workitem.attribute.user.role.condition.providers.serverdeploy as displayed below.

Prepare Server Update Site

Open the conf/ccm folder for your deployed server (Try this on a test server first). Open the folder \JazzTeamServer\server\conf\ccm like shown below.

Server Configuration Folder

In the project com.ibm.js.team.workitem.attribute.user.role.condition.providers.serverdeploy, select the folders provision_profiles and sites. Then select Copy.

Copy Server Extension

In the folder \JazzTeamServer\server\conf\ccm paste the folders and files you just copied. Acknowledge overwriting folders (and files if the extension has been deployed).

Request a server reset and restart the server. See Is The Extension Deployed? How Can I Redeploy? for details.

Check if the server Extension is deployed as described in Is The Extension Deployed? How Can I Redeploy? Search for the component com.ibm.js.team.workitem.attribute.user.role.condition.providers.component.

Chack Component DeployedThe condition provider is now deployed on your CCM server.

Deploy on the RTC Eclipse Client

The condition needs to be installed on an Eclipse client to be configured. It also needs to be installed on all Eclipse Clients that are used by users that have to use this condition.

It is possible to install this in an Eclipse client (installed from a zip file) and ship the Eclipse client with the extension installed by zipping it up again and providing the zip file.

For users that use the Web UI, the Condition works as soon as it is set up in the process.

To install the extension on an Eclipse client start Eclipse and select the menu Help>Install New Software…

Install Client Extension Step 1

In the install wizard select add.

Install Client Extension Step 2Then select Local and browse to the folder with your project com.ibm.js.team.workitem.attribute.user.role.condition.providers.updatesite

Select the User Role Condition Providers Feature, you might have to deselect the check boxes like below to see it.

Install Client Extension Step 3Press next and follow the wizard to install the extension. Restart the client.

Configure the Project

Open an Eclipse client that has the condition installed to configure it. Open the project area to configure its process.

Create Conditions for the Attributes

Open the Process Configuration. Select Process Configuration>Project Configuration>Configuration Data>Work Items>Attribute Customization.
For each attribute that needs to be read only or required, use the Add button to add a new condition.

Create New Conditions

Provide a name for the condition. The best approach is to name the condition in a naming schema that contains the usage of the condition and the attribute name. As an example name it Read_Only_AttributeName or Required_AttributeName. the reason will become apparent later. It basically helps finding it later to configure it in the operational behavior and the process XML.

You should now see the providers that you deployed in addition to Java Script. Select the provider that works best for you.

Select the ProviderPlease note, if you have groups of attributes that behave the same for all role configurations, you can create one condition for this group, instead of creating it for only one attribute.

After you created your configurations the Attribute Customization section should look as below.

Created Conditions

Configure the Operational Behavior

After creating the conditions needed, the next step is to activate the conditions for the attributes in the operational behavior.

Select Process Configuration>Team Configuration>Operation Behavior.

Select the “Everyone” role and add the preconditions Required Attributes For Condition and Read Only Attributes For Condition.

Precondition Configuration

Please note: if you want to configure this for another role, you have to configure the conditions for that role as well. How to do this efficiently is described below.

For each condition you created add a configuration for the related precondition. Select the condition and the attribute that the condition governs.

Configure Attribute In PreconditionIf you have groups of attributes that behave the same across all workflows and roles, you can use one condition for that group and select all affected attributes here.

The image below shows an example configuration.

Configured Example
Configure the Conditions in the Process Configuration Source

The Conditions don’t have any configuration for workflow states, work item categories and related roles yet. This needs to be done last. To configure the conditions it is necessary to add information to the process configuration source.
Locate the conditions in the process configuration source e.g. by searching for the name of a condition. The conditions are all in one block.

Configure Conditions In Process Configuration Source

For each condition, remove the closing /at the end of the condition element and add a new ending tag .

Configure Condition Step 1

The data should now look like the image below and there should be no errors. If there are errors, correct your XML.

Configure Condition Step 2

Now add the type, state and role configuration for each condition.

Configure Condition Step 3Configure Condition Step 4

You can configure for each condition for which work item type category (work item types with the same workflow), for which state, which roles should match the condition.

Save your work!

Test your work

To test, create a work item and move it through the workflow using user ID’s with role configurations that match your expectation. Make sure the user has roles that match the condition configuration.

Test ConditionIn this case the Attribute Filed Against is required due to the roles of the user and the description is read only in the state. Please note, that the work item is not yet initialized, but the target state is new. The section below explains the format and how to retrieve the data.

Retrieving the Configuration Data

To configure the condition, it is necessary to get the data to do the configuration. This section describes how to get the data.
The State ID’s and the Work Item Type categories as well as the Role ID’s can be retrieved from the process template.

The work item type category can be found in the web UI as well as in the Eclipse UI.
Find Work Item CategoryThe work item type category here is: “com.ibm.team.workitem.workItemType“.

To find the workflow state names use the Eclipse client and search the process configuration source  for the workflow name e.g. Defect Workflow in this case. Scroll down to find the state elements.

Find Workflow State NamesLook up the state ID’s for the states and document them. This is a one-time action and only needs to be maintained if you change workflows and add states.

The state New has the identifier “s1”, the state In Progress has id=”S2” etc.

To find the role Identifiers, open the project area or team area and select the roles.

Find Role Identifiers Step 1

The role test1 has the identifier “test1” the role Product Owner has the identifier “Product Owner”.

Find Role Identifiers Step 2The role “Everyone” has the identifier “default”.

The configuration below configures the condition to return true for

  • a work item of this type category (a defect) in the New state “s1” for the role “test1”.
  • a work item of this type category (a defect) in the Resolved state “s3” for the role “test1”.
  • a work item of this type category (a defect) in the In Progress state “s3” for the roles “test1”, “test2” and Everyone.

Configuration ExampleSee another more complex example below.

Configuration Example 2

Configuring the Operational Behavior for Multiple Roles

Operational behavior still works as explained in Process behavior lookup in Rational Team Concert 2.0. If it is necessary to configure the operational behavior for multiple roles but to make sure the Required and Read Only Attributes work the same as in the configuration for the Everyone role, this can be easily achieved.

Configure the other role(s).

Multi Role Behavior 1

When configuring the operational behavior name the precondition with the role included. For example Required Attributes For Condition Everyone.

Configure the operational behavior for the new role including the Role Name but don’t configure anything. For example name it Required Attributes For Condition Role1.

Search for the configuration for the configured role in the process configuration source.

Multi Role Behavior 2Copy the configuration details into a file. The interesting parts are the sections and <readOnlyAttributes….> .

Now search for the configuration for the new role the same way. Copy the XML with the configuration for the Required Attributes into the operational behavior configuration.   Copy the XML with the configuration for the Read-Only Attributes into the operational behavior configuration.

Save your work!

Since the conditions are configured globally you have essentially cloned the configuration for the other role.

Summary

Using a custom condition and the out of the box operational behavior for Required Attributes For Condition and Read-Only Attributes For Condition, allows to achieve the required behavior.

Keep in mind this is by no means production code. You might want to do more testing.

As always I hope this helps someone out there to get their job done more efficient.

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.

A RTC WorkItem Command Line Version 2.2


Creating links is not easy. Many things can go wrong.  Testing by a user showed that there was an issue with links between work items and build results. I found that I got the link direction wrong. I fixed that. Here is the updated source code.

Latest Version

See A RTC WorkItem Command Line Version 3.0 for the latest version.

Related posts

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

You can download the latest version here:

Setup and Usage

Follow the description in A RTC WorkItem Command Line Version 2 and in A RTC WorkItem Command Line Version 2.1. Check the README.txt which is included in the downloads.

Extending the WorkItem Command Line With New Commands


If one needs a new command to be run in the WorkItem Command Line, what needs to be done? Lets explore this with the background from the post The WorkItem Command Line Explained.

As always, keep in mind, this is not production code, under development and not designed to win a beauty pageant. Testing has not been very thorough, so using this code is at your own risk.

Latest Version

See A RTC WorkItem Command Line Version 3.0 for the latest version.

The New Command

The new command is supposed to help with migrating Multi Select Enumeration  attributes available until RTC 3.x to Enumeration List attributes available in RTC 4.x and later.

As explained in Workaround: Migrate from Rational Team Concert 3.X string attribute used as multi-select lists to RTC 4.X enumeration lists, in RTC 3.x these multi select lists where implemented as a string attribute, that stored the enumeration literal ID’s separated by comma. A special presentation handled input and output. Downside of this approach was, that it was hard to query these attributes and create dashboards and reports. The new way is a special list type for each enumeration.

The workaround explains how to use CSV export and import to migrate the data. This solution uses the API to do so. What this solution does not attempt so far is having a mapping that changes the data.

Related posts

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

Download the latest version that includes the code from this post.

Requirements

The command is supposed to work on work items of a specific type in a project area.

  • For a source attribute (of type string), read the value, split it into enumeration literal ID’s
  • Find the enumeration literals and build a list from them
  • Store the list of enumeration literals in a target attribute
  • Perform this for one work item of the type (e.g. for testing)
  • Perform this for all work items of the type
  • Be able to tolerate errors and run nonetheless

The image below shows the source attribute as shown in RTC 4.x with a regular string presentation and the new EnumerationList attribute. The values from the source attribute have been migrated over to the target attribute.

Source and target attribute

Source and target attribute

Creating the new Command

The first step is to create a new command class. As explained in The WorkItem Command Line Explained there are some abstract classes available to be used. The AbstractWorkItemModificationCommand seems to be it, but looking closer, it is very specialized in creating or modifying a work item with a list of provided attributes and values in a parameter list. So the choice here is the AbstractTeamRepositoryCommand instead.

So the first step is to create the class MigrateWorkItemAttributeCommand in the package com.ibm.js.team.workitem.commandline. Create a public constructor as required from the abstract class and override the abstract methods. In the method getCommandName() return the name of the command. Also override the method setRequiredParameters() but make sure to add a call to super() to get the parameters it requires added. The initial step would look similar to this:

public class MigrateWorkItemAttributeCommand extends
		AbstractTeamRepositoryCommand {

	public MigrateWorkItemAttributeCommand(ParameterManager parametermanager) {
		super(parametermanager);
	}

	/***
	 * Add required parameters
	 * 
	 * (non-Javadoc)
	 * 
	 * @see com.ibm.js.team.workitem.commandline.framework.AbstractTeamRepositoryCommand#setRequiredParameters()
	 */
	@Override
	public void setRequiredParameters() {
		super.setRequiredParameters();
	}

	/***
	 * Return the command
	 * 
	 * (non-Javadoc)
	 * 
	 * @see com.ibm.js.team.workitem.commandline.framework.IWorkItemCommand#getCommandName()
	 */
	@Override
	public String getCommandName() {
		return "migrateattribute";
	}

	/***
	 * Perform the command
	 * 
	 * (non-Javadoc)
	 * 
	 * @see com.ibm.js.team.workitem.commandline.framework.AbstractCommand#process()
	 */
	@Override
	public OperationResult process() throws TeamRepositoryException {
		return getResult();
	}
}

In addition to the repository URL, the user name and the password, the command needs to know the project area, and the the work item type. It also needs the parameter for the source attribute ID and the target attribute ID. Some flags like ignoreErrors might come in handy as well.

Most of this can be harvested from existing commands like the CreateWorkItemCommand.  A few new need to be added. Introducing some constants is a good idea for later. The code added to setRequiredParameters() would finally look like below. The call to super() makes sure the basic parameters for the login process are required as well.

public static final String COMMAND_MIGRATE_ENUMERATION_LIST_ATTRIBUTE = "migrateattribute";
public static final String PARAMETER_SOURCE_ATTRIBUTE_ID = "sourceAttributeID";
public static final String PARAMETER_SOURCE_ATTRIBUTE_ID_EXAMPLE = "com.acme.custom.enum.multiselect";
public static final String PARAMETER_TARGET_ATTRIBUTE_ID = "targetAttributeID";
public static final String PARAMETER_TARGET_ATTRIBUTE_ID_EXAMPLE = "com.acme.custom.enum.list";


/***
 * Add required parameters
 * 
 * (non-Javadoc)
 * 
 * @see com.ibm.js.team.workitem.commandline.framework.AbstractTeamRepositoryCommand#setRequiredParameters()
 */
@Override
public void setRequiredParameters() {
	super.setRequiredParameters();
	// Copied from CreateWorkItemCommand
	getParameterManager()
			.syntaxAddRequiredParameter(
					IWorkItemCommandLineConstants.PARAMETER_PROJECT_AREA_NAME_PROPERTY,
					IWorkItemCommandLineConstants.PARAMETER_PROJECT_AREA_NAME_PROPERTY_EXAMPLE);
	getParameterManager()
			.syntaxAddRequiredParameter(
					IWorkItemCommandLineConstants.PARAMETER_WORKITEM_TYPE_PROPERTY,
					IWorkItemCommandLineConstants.PARAMETER_WORKITEM_TYPE_PROPERTY_EXAMPLE);
	getParameterManager().syntaxAddSwitch(
			IWorkItemCommandLineConstants.SWITCH_IGNOREERRORS);
	getParameterManager().syntaxAddRequiredParameter(
			PARAMETER_SOURCE_ATTRIBUTE_ID,
			PARAMETER_SOURCE_ATTRIBUTE_ID_EXAMPLE);
	getParameterManager().syntaxAddRequiredParameter(
			PARAMETER_TARGET_ATTRIBUTE_ID,
			PARAMETER_TARGET_ATTRIBUTE_ID_EXAMPLE);
}

The method getCommandName() would look like this now, with the string extracted into a constant:

	/***
	 * Return the command
	 * 
	 * (non-Javadoc)
	 * 
	 * @see com.ibm.js.team.workitem.commandline.framework.IWorkItemCommand#getCommandName()
	 */
	@Override
	public String getCommandName() {
		return COMMAND_MIGRATE_ENUMERATION_LIST_ATTRIBUTE;
	}

Add The New Command to the Main Class

The new command needs to be added to the WorkItem Command Line. In the class WorkitemCommandLine add it to the method method addSupportedCommands() like shown below.

/**
 * Add the supported commands. If introducing a new command, add it here.
 * 
 * @param parameterManager
 */
private void addSupportedCommands(ParameterManager parameterManager) {
	addSupportedCommand(new PrintTypeAttributesCommand(
			new ParameterManager(parameterManager.getArguments())));
	addSupportedCommand(new CreateWorkItemCommand(new ParameterManager(
			parameterManager.getArguments())));
	addSupportedCommand(new UpdateWorkItemCommand(new ParameterManager(
			parameterManager.getArguments())));
	addSupportedCommand(new MigrateWorkItemAttributeCommand(new ParameterManager(
			parameterManager.getArguments())));
}

Now it can be used like shown below. It is also possible to start debugging it using Launches.

wcl -migrateattribute /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph projectArea="JKE Banking (Change Management)" workItemType=task sourceAttributeID=custom.medium.string targetAttributeID=custom.enumeration.list

or this

wcl -migrateattribute /ignoreErrors repository="https://clm.example.com:9443/ccm" user=ralph password=ralph projectArea="JKE Banking (Change Management)" workItemType=task  id=275 sourceAttributeID=custom.medium.string targetAttributeID=custom.enumeration.list

Implementing the Command Logic

Now the method process() needs to be implemented. It orchestrates the work to be done. This obviously took some iterations. Here is the final result.

/***
 * Perform the command
 * 
 * (non-Javadoc)
 * 
 * @see com.ibm.js.team.workitem.commandline.framework.AbstractCommand#process()
 */
@Override
public OperationResult process() throws TeamRepositoryException {
	// From CreateWorkItemCommand
	// Get the parameters such as project area name and Attribute Type and
	// run the operation
	String projectAreaName = getParameterManager()
			.consumeParameter(
					IWorkItemCommandLineConstants.PARAMETER_PROJECT_AREA_NAME_PROPERTY)
			.trim();
	// Find the project area
	IProjectArea projectArea = ProcessAreaUtil.findProjectArea(
			projectAreaName, getProcessClientService(), getMonitor());
	if (projectArea == null) {
		throw new WorkItemCommandLineException("Project Area not found: "
				+ projectAreaName);
	}

	String workItemTypeID = getParameterManager().consumeParameter(
			IWorkItemCommandLineConstants.PARAMETER_WORKITEM_TYPE_PROPERTY)
			.trim();
	// Find the work item type
	IWorkItemType workItemType = WorkItemHelper.findWorkItemType(
			workItemTypeID, projectArea.getProjectArea(),
			getWorkItemCommon(), getMonitor());

	// Get the parameter values - The source attribute
	String sourceAttributeID = getParameterManager().consumeParameter(
			PARAMETER_SOURCE_ATTRIBUTE_ID).trim();
	// check if old attribute ID is string type
	IAttribute sourceIAttribute = getWorkItemCommon().findAttribute(
			projectArea, sourceAttributeID, getMonitor());
	if (sourceIAttribute == null) {
		throw new WorkItemCommandLineException(
				"Source Attribute not found: " + sourceAttributeID);
	}
	if (!AttributeTypes.STRING_TYPES.contains(sourceIAttribute
			.getAttributeType())) {
		throw new WorkItemCommandLineException(
				"Source Attribute is not a String type: "
						+ sourceAttributeID);
	}

	// Get the parameter values - The target attribute
	String targetAttributeID = getParameterManager().consumeParameter(
			PARAMETER_TARGET_ATTRIBUTE_ID).trim();
	// check if new attribute ID is EnumerationList
	IAttribute targetIAttribute = getWorkItemCommon().findAttribute(
			projectArea, targetAttributeID, getMonitor());
	if (targetIAttribute == null) {
		throw new WorkItemCommandLineException(
				"Target Attribute not found: " + targetAttributeID);
	}
	if (!AttributeTypes.isEnumerationListAttributeType(targetIAttribute
			.getAttributeType())) {
		throw new WorkItemCommandLineException(
				"Target Attribute is not an EnumerationList: "
						+ targetAttributeID);
	}
	if (getParameterManager().hasSwitch(
			IWorkItemCommandLineConstants.SWITCH_IGNOREERRORS)) {
		setIgnoreErrors();
	}
	String wiID = getParameterManager().consumeParameter(
			IWorkItemCommandLineConstants.PARAMETER_WORKITEM_ID_PROPERTY);
	if (wiID != null) {
		IWorkItem wi = WorkItemHelper.findWorkItemByID(wiID,
				IWorkItem.SMALL_PROFILE, getWorkItemCommon(), getMonitor());
		if (!wi.getWorkItemType().equals(workItemType.getIdentifier())) {
			throw new WorkItemCommandLineException(
					"Work item type mismatch: "
							+ workItemType.getIdentifier() + " specified "
							+ workItemType.getIdentifier());
		}
		migrateSingleWorkItem(wi, sourceIAttribute, targetIAttribute);
	} else {
		// Update all work items of this type.
		migrateAllWorkItems(projectArea, workItemType, sourceIAttribute,
				targetIAttribute);
	}
	// If we got here, we succeeded
	getResult().setSuccess();
	return getResult();
}

What it basically does is to get the parameters needed for this command and work with them. Keep in mind that the abstract class AbstractTeamRepositoryCommand does the login process already. So what needs to be done is to get the project area, the work item type, the source and the target attribute ID and the flags. Most of the code has been harvested in existing commands as well as in the WorkItemHelper class. The methods migrateSingleWorkItem() and migrateAllWorkItems() will be explained in a bit.

Some new fields and getters/setters where introduced in the process. We also need the separator for the literals in the original string later. The code looks as below:

public static final String SEPARATOR_ENUMERATION_LITERAL_ID_LIST = ",";

private boolean fIgnoreErrors = false;

private void setIgnoreErrors() {
	fIgnoreErrors = true;
}

private boolean isIgnoreErrors() {
	return fIgnoreErrors;
}

public MigrateWorkItemAttributeCommand(ParameterManager parametermanager) {
	super(parametermanager);
}

There was also a need to be able to access the ITeamRepository in the MigrateWorkItemAttributeCommand. I introduced a getter into the AbstractTeamRepositoryCommand class for convenience.

Note, it is always possible to get the ITeamRepository from an RTC object using the getOrigin() method and cast it to the ITeamRepository. However, it makes sense to add this to the framework, too.

Implementing the Work Item Modification

The final implementation steps are to implement the missing methods as well as add a inner class extending WorkItemOperation, to do the change to the work item. The code is shown below.

/**
 * Migrate one specific work item - for testing
 * 
 * @param wi
 * @param sourceIAttribute
 * @param targetIAttribute
 * @throws TeamRepositoryException
 */
private void migrateSingleWorkItem(IWorkItem wi,
		IAttribute sourceIAttribute, IAttribute targetIAttribute)
		throws TeamRepositoryException {
	MigrateWorkItem operation = new MigrateWorkItem("Migrate",
			IWorkItem.FULL_PROFILE, sourceIAttribute, targetIAttribute);
	performMigration((IWorkItemHandle) wi.getItemHandle(), operation);
}

/**
 * Migrate all work items of a specific type in a project area
 * 
 * @param projectArea
 * @param workItemType
 * @param sourceIAttribute
 * @param targetIAttribute
 * @throws TeamRepositoryException
 */
private void migrateAllWorkItems(IProjectArea projectArea,
		IWorkItemType workItemType, IAttribute sourceIAttribute,
		IAttribute targetIAttribute) throws TeamRepositoryException {
	// Find all work items of this type.
	// Create an Expression to find them
	IQueryableAttribute attribute = QueryableAttributes.getFactory(
			IWorkItem.ITEM_TYPE).findAttribute(projectArea,
			IWorkItem.PROJECT_AREA_PROPERTY, getAuditableCommon(),
			getMonitor());
	IQueryableAttribute type = QueryableAttributes.getFactory(
			IWorkItem.ITEM_TYPE).findAttribute(projectArea,
			IWorkItem.TYPE_PROPERTY, getAuditableCommon(), getMonitor());
	Expression inProjectArea = new AttributeExpression(attribute,
			AttributeOperation.EQUALS, projectArea);
	Expression isType = new AttributeExpression(type,
			AttributeOperation.EQUALS, workItemType.getIdentifier());
	Term typeinProjectArea = new Term(Term.Operator.AND);
	typeinProjectArea.add(inProjectArea);
	typeinProjectArea.add(isType);

	// Run the Expression
	IQueryClient queryClient = getWorkItemClient().getQueryClient();
	IQueryResult results = queryClient.getExpressionResults(
			projectArea, typeinProjectArea);
	// Override the result set limit so that we get more than 1000 items if
	// there are more
	results.setLimit(Integer.MAX_VALUE);
	MigrateWorkItem operation = new MigrateWorkItem("Migrate",
			IWorkItem.FULL_PROFILE, sourceIAttribute, targetIAttribute);
	// Run the operation for each result
	while (results.hasNext(getMonitor())) {
		IResult result = (IResult) results.next(getMonitor());
		performMigration((IWorkItemHandle) result.getItem(), operation);
	}
}

/**
 * Perform the update and
 * 
 * @param handle
 * @param operation
 * @throws WorkItemCommandLineException
 */
private void performMigration(IWorkItemHandle handle,
		MigrateWorkItem operation) throws WorkItemCommandLineException {
	String workItemID = "undefined";
	try {
		IWorkItem workItem = getAuditableCommon().resolveAuditable(
				(IWorkItemHandle) handle, IWorkItem.SMALL_PROFILE,
				getMonitor());
		workItemID = getWorkItemIDString(workItem);
		operation.run(handle, getMonitor());
		getResult().appendResultString(
				"Migrated work item " + workItemID + ".");
	} catch (TeamRepositoryException e) {
		throw new WorkItemCommandLineException(
				getResult().getResultString()
						+ "TeamRepositoryException: Work item "
						+ workItemID + " attribute not migrated. "
						+ e.getMessage(), e);
	} catch (WorkItemCommandLineException e) {
		String message = "WorkItemCommandLineException Work item " + workItemID
				+ " attribute not migrated. " + e.getMessage();
		if (!isIgnoreErrors()) {
			throw new WorkItemCommandLineException(getResult().getResultString() + message, e);
		} else {
			getResult().appendResultString(message);
		}
	}
}

The method migrateSingleWorkItem() basically runs the migration operation for a single work item. It creates the inner class MigrateWorkItem with the data it needs and then calls the method performMigration() to perform the migration. The method performMigration() calls the provided method and does all the error handling.

The method migrateAllWorkItems() basically creates an expression to get all work items of the specified type from the project area. The expression is run and performMigration() is performed on one result after the other.

Please note that the line

results.setLimit(Integer.MAX_VALUE);

makes sure that the built in query limit of the Eclipse client is overwritten to allow getting all possible results. To work it has to be exactly at the place it is.

The method performMigration() just performs the call and wraps everything up to be able to process exceptions. The information is provided in the result.

The methods above use some additional methods to get the client library IWorkItemClient to be able to perform the query expression.

/**
 * We need this client libraries to run queries
 * 
 * @return
 */
private IWorkItemClient getWorkItemClient() {
	return (IWorkItemClient) getTeamRepository().getClientLibrary(
			IWorkItemClient.class);
}

/**
 * Get the work item ID as string
 * 
 * @param workItem
 * @return
 */
private String getWorkItemIDString(IWorkItem workItem) {
	return new Integer(workItem.getId()).toString();
}

The WorkItemOperation Implementation

Finally lets look at the inner class MigrateWorkItem which extends WorkItemOperation. I use this approach in all the work item manipulation client API usage, because it conveniently handles all possible mishaps.

private class MigrateWorkItem extends WorkItemOperation {
	
	IAttribute fsourceAttribute = null;
	IAttribute fTargetAttribute = null;
	
	/**
	 * Constructor
	 * 
	 * @param The
	 *            title message for the operation
	 * @param message
	 * @param profile
	 * @param sourceAttribute
	 * @param targetAttribute
	 */
	public MigrateWorkItem(String message, ItemProfile profile,
			IAttribute sourceAttribute, IAttribute targetAttribute) {
		super(message, profile);
		fsourceAttribute = sourceAttribute;
		fTargetAttribute = targetAttribute;
	}
	
	/***
	 * This gets called if run() is called
	 * 
	 * @see com.ibm.team.workitem.client.WorkItemOperation#execute(com.ibm.team
	 *      .workitem.client.WorkItemWorkingCopy,
	 *      org.eclipse.core.runtime.IProgressMonitor)
	 */
	@Override
	protected void execute(WorkItemWorkingCopy workingCopy,
			IProgressMonitor monitor) throws TeamRepositoryException,
			RuntimeException {
	
		IWorkItem workItem = workingCopy.getWorkItem();
		String thisItemID = getWorkItemIDString(workItem);
		if (!workItem.hasAttribute(fsourceAttribute)) {
			throw new WorkItemCommandLineException(
					"Work Item "
							+ thisItemID
							+ " Source Attribute not available - Synchronize Attributes: "
							+ fsourceAttribute.getIdentifier());
		}
		if (!workItem.hasAttribute(fTargetAttribute)) {
			throw new WorkItemCommandLineException(
					"Work Item "
							+ thisItemID
							+ " Target Attribute not available - Synchronize Attributes: "
							+ fTargetAttribute.getIdentifier());
		}
		// get the old value - a string with literals separated by a comma
		Object ovalue = workItem.getValue(fsourceAttribute);
		// compute the result values
		String sourceValues = "";
		if (null != ovalue && ovalue instanceof String) {
			sourceValues = (String) ovalue;
		}
		if (!sourceValues.equals("")) {
			String[] values = sourceValues
					.split(SEPARATOR_ENUMERATION_LITERAL_ID_LIST);
			IEnumeration enumeration = getWorkItemCommon()
					.resolveEnumeration(fTargetAttribute, monitor);

			List results = new ArrayList();
			for (String literalID : values) {
				if (literalID == "") {
					// Nothing to do
					continue;
				}
				Identifier literal = getLiteralEqualsIDString(
						enumeration, literalID);
				if (null == literal) {
					throw new WorkItemCommandLineException("Work Item "
							+ thisItemID
							+ " Target literal ID not available: "
							+ literalID + " Attribute "
							+ fTargetAttribute.getIdentifier());
				}
				results.add(literal);
			}
			// Set the value
			workItem.setValue(fTargetAttribute, results);
		}
		getResult().appendResultString("Migrated work item " + thisItemID);
	}

	/**
	 * Gets an enumeration literal for an attribute that has the specific
	 * literal ID.
	 * 
	 * @param enumeration
	 *            - the enumeration to look for
	 * @param literalIDString
	 *            - the literal ID name to look for
	 * @return the literal or null
	 * @throws TeamRepositoryException
	 */
	private Identifier getLiteralEqualsIDString(
			final IEnumeration enumeration,
			String literalIDString) throws TeamRepositoryException {
		List literals = enumeration
				.getEnumerationLiterals();
		for (Iterator iterator = literals.iterator(); iterator
				.hasNext();) {
			ILiteral iLiteral = (ILiteral) iterator.next();
			if (iLiteral.getIdentifier2().getStringIdentifier()
					.equals(literalIDString.trim())) {
				return iLiteral.getIdentifier2();
			}
		}
		return null;
	}
}

The interesting method here is execute(). It basically checks if the work item has the source attribute and the target attribute. If one of the attributes is missing, it can not run. You have to synchronize attributes first.

As last step the method gets the string value and splits the string into its values. The list of enumeration literal ID’s is then used to look up the enumeration literals. In case a literal can not be found, this is an error. If the literals are retrieved, they are put into a list and finally set as the value for the new attribute.

The method getLiteralEqualsIDString() is actually mostly harvested from the WorkItemHelper class, where it is used with the literal display name.

Additional Changes to the Framework

Since this command works very different than the others, it became apparent that it would be nice to see progress directly, if we are not working in RMI server mode. this also helps the other commands and potential future commands. To provide a better user experience

    • In WorkitemCommandLine the method isServer() has been made public so that it can be read from the framework classes

 

  • In OperationResult the method appendResultString() was changed to output the information rather than storing it in a result string, if the WorkItemCommandLine does not run in server mode

This change makes the result output much more immediate, especially for long running commands in normal mode. In RMI mode, the data is provided as is, after the call finishes. Some Observations The Parameter handling does not handle optional parameters well. It should be possible to add an optional parameter like the work item ID in this case. However, this is just a minor problem here. In addition at some point it can get so complicated that the automation for printing the command syntax won’t be easily manageable any more. Summary This post explains how to extend the WorkItem Command Line with your own commands. As always, I hope that helps someone out there.