Work Item cross-stream deliveries and promotion – a sample precondition

Work Item based Promotion allows you to move your changes related to a certain work item in your streams hierarchy. At the same time, Rational Team Concert allows a great flexibility to “move” your changes between work items, so developers can report their work or decide which changes better represent their tasks work. However, when it comes to work items promotion, there are times where this flexibility can lead you to undesired scenarios.


In this post I am going to publish 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. Please also remember, as stated in the disclaimer, that this code comes with lack of promise or guarantee. At the end of the post you will find the complete code solution to be downloaded.

*Update 2014/04/03*: updated the downloadable code to remove a dependency not needed. Note also that the client side (UI), has been developed against 4.0.6 SDK. Some people has experienced issues locating the “WorkspaceAndStreamSelectionDialogOptions” class in a 4.0.1 SDK.

The issue

The potential case I wanted to discuss can be summarized in the following scenario: you have one work item with two change sets. Each change set has been delivered to a different stream, which belong to different stream hierarchies. If now we would like to promote the work item in one of these stream hierarchies:

promoIssueIn such case, the promotion request will end up in an error like the following:

promoErrorThat is because promotion checks that all changes can be moved in the streams promotion hierarchy. In our sample scenario, as the change sets in “DEV2 stream” cannot be moved to “QA1 stream” which is the target of the promotion (because they are changes in a different component, or not delivered to “DEV1 stream” yet).

What are the solutions to this blocking point? Mainly two:

  1. To deliver the “offending” change set to “DEV1 stream”
  2. To disassociate the “offending” change set

A potential workaround

However the way of solving this situation, I would like to introduce in this blog post a potential customization workaround. Basically the concept is summarized in the following diagram:

summaryPromoThe idea behind is preventing that situation to happen, checking at deliver time if the change set is linked to a work item. If so, checking if the work item has additional change sets contained in a different stream.

Precondition solution

I wanted to blog about a possible precondition that will automate the introduced “workaround”.

Specifying the streams

Firstly we need to specify which streams are to be “guarded”. In other words, to which streams we don’t allow this parallel changes on a work item. We can’t check it generally over all the streams, because we would also block a normal promotion operation, and there would be other teams/streams that want this flexibility (e.g. distributed teams). To accomplish that, in this example, we provide with the ability of specifying a list of streams, or a stream name pattern:

preconditionConfigChecking the conditions

I will focus on the specific parts of the precondition code that perform the checks, and leave the rest. The rest is not actually the most interesting part of the code for this discussion.

From the deliver information, and once the information about the guarded streams have been retrieved, we get the information of the change sets that are part of the deliver. This is stored in a map of change set as key, and target workspace of the deliver as value.

 /*
 * For each change set involved in the deliver
 * Add them to a map with the target workspace they are being delivered to
 * 
 */
 
 for (IChangeHistoryAddOperandDelta delta : deliverData.getDeltas(IChangeHistoryAddOperandDelta.class)) 
 {
 for (IComponentHandle comp : delta.getComponents()) 
 {
 
 for (IChangeSetHandle cs : delta.getAdditionalBaselineChangeSets(comp))
 {
 changes.add(cs);
 targetWorkspaceByCsId.put(cs.getItemId(), delta.getTargetWorkspace());
 }
 
 for (IChangeSetHandle cs : delta.getAdditionalChangeSets(comp)) {
 changes.add(cs);
 targetWorkspaceByCsId.put(cs.getItemId(), delta.getTargetWorkspace());
 }
 }
 
 }

This piece of code has nothing of original and can be found in the SDK in other out-of-the-box preconditions. For those change sets, we get the work items they are linked to, and then we build a map which consists of the work item as key and the linked change sets as value. This is what we are going to check:

// Get a map of the change sets and the work items they are linked to
 
 ServerProviderFactory provider = new ServerProviderFactory(this, getRepositoryItemServiceForScm());
 
 // Get the work items
 
 Map<IChangeSet, List<IAuditable>> links = ChangeSetLinks.resolveLinks(provider, changes, ILinkConstants.CHANGESET_WORKITEM_LINKTYPE_ID, monitor.newChild(1));
 
 // Map of work items and the linked change sets that are part of the original deliver
 
 Map<IWorkItem, List<IChangeSetHandle>> workchanges = invertCSMapChanges(links, monitor.newChild(1));
 
 // Validate for every work item and change sets linked to it 
 
 for (Entry<IWorkItem, List<IChangeSetHandle>> entry : workchanges.entrySet())
 {
 if(validateChanges(entry.getKey(), entry.getValue(), devStreams, targetWorkspaceByCsId, monitor.newChild(1), collector))
 return; // To modify this line if we want to report all the offending work items, not just the first
 }

The main part of the check is based on comparison. Comparing each deliver “target workspace” (target stream if it is a guarded one), with the rest of guarded streams. If the comparison throws incoming information, this incoming can be:

a) Change sets: we check any of the incoming change sets is one of the change sets linked to the work item we are verifying. Note that change sets in the incoming will appear if components are shared between the streams (and there are differences in changes in these components):

if(!incoming.isEmpty())
{
  for (IChangeSetHandle currentCheck : changesToCheck) 
    {
      for (IChangeSetHandle currentIncoming : incoming)
	{
	  if(currentCheck.sameItemId(currentIncoming))
	  {
	    IAttribute typeAtt = getWorkItemServer().findAttribute(workitem.getProjectArea(), IWorkItem.TYPE_PROPERTY, progress);
	    // Illegal condition. Report the problem. This can be modified to hold all offending cases and report all at once, depending on needs
            IAdvisorInfo info = collector.createProblemInfo(Messages.PreventCrossDeliverAdvisor_4, NLS.bind(Messages.PreventCrossDeliverAdvisor_5, new Object[] {workitem.getValue(typeAtt).toString(), workitem.getId()}), ID_DELIVER_PROBLEM);
            collector.addInfo(info);
	    return true;
	}
}

b) Components: these will appear in the incoming if there are differences in the components configuration between the streams. In this case, the check is to verify if any of the change sets that are linked to the work item exist in these components that differ between the streams:

else
{
  /*
   *
   * Maybe the streams have different components?
   * If the incoming are components, check if the change sets linked are contained in the incoming components
   * 
   */
				
   List diffComponents = checkDifferentComponets(syncReport.localComponents(), syncReport.remoteComponents());

   if(! diffComponents.isEmpty())
   {
					
     IItem[] itemChanges = getRepositoryItemServiceForScm().fetchItems(changesToCheck.toArray(new IChangeSetHandle[changesToCheck.size()]), new String[]{IChangeSet.COMPONENT_PROPERTY});
     for (IItem currentCheck : itemChanges) 
     {
	if((currentCheck instanceof IChangeSet) && (checkChangeComponent(diffComponents,(IChangeSet) currentCheck)))
	{
	 IAttribute typeAtt = getWorkItemServer().findAttribute(workitem.getProjectArea(), IWorkItem.TYPE_PROPERTY, progress);
	 // Illegal condition. Report the problem. This can be modified to hold all offending cases and report all at once, depending on needs
	 IAdvisorInfo info = collector.createProblemInfo(Messages.PreventCrossDeliverAdvisor_4, NLS.bind(Messages.PreventCrossDeliverAdvisor_9, new Object[] {workitem.getValue(typeAtt).toString(), workitem.getId()}), ID_DELIVER_PROBLEM);
	 collector.addInfo(info);
	 return true;
     }
  }
					
}

If none of these conditions are met, the deliver will continue normally, assuming we are not running into the condition that would block our promotion process.

Conclusion

I know this case is very specific to applications development on system Z and the promotion feature. Actually solving this case manually is not such a big burden, but this early check can solve some issues or alleviate the administration overhead in big Rational Team Concert deployments.

The code can be downloaded from here. Feel free to use and modify it: if you try to reuse it in more complex scenarios than the one I tested you may need to extend it, but I hope this provides you with a good starting point.

Acknowledgement: thanks to David Bellagio in helping me test this experiment.

Advertisements

2 thoughts on “Work Item cross-stream deliveries and promotion – a sample precondition

  1. Pingback: Unique Files Names SCM Advisor | Jorge Díaz's Blog

Leave a Reply

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

WordPress.com Logo

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

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s