It's all about the answers!

Ask a question

Links created through server API cause constant "Refresh to get..." in Web UI


Uwe Berthold (6637) | asked Nov 28 '18, 8:45 a.m.

Hi everyone,


I've got the following problem using the server API for RTC 6.0.6.

I've written an IOperationParticipant which creates work items if certain preconditions are met.
Those new work items should be linked to the work item that causes the call to the operation participant via the "contributes to" link type (constant WorkItemLinkTypes.CONTRIBUTES_TO_WORK_ITEM).

The following is the essence of my code that does the linking, adapted from code found here in the forum and Ralph Schoon's Blog (and again stripped down to the essence for posting here):
    // Create references
    IReferenceFactory refFactory = IReferenceFactory.INSTANCE;
    IReference parent = refFactory.createReferenceToItem(parentWI.getItemHandle());
    IReference child = refFactory.createReferenceToItem(childWI.getItemHandle());

    // Create the link
    ILinkService linkService = getService(ILinkService.class);
    ILinkServiceLibrary linkServiceLibrary = (ILinkServiceLibrary) linkService.getServiceLibrary(ILinkServiceLibrary.class);
    ILink link = linkServiceLibrary.createLink(linkType, child, parent);

    // Save the link
    linkServiceLibrary.saveLink(link);
In above code, linkType is a method parameter whose value comes from configuration, but is essentially one of the constants in class WorkItemLinkTypes.
It works well and expected for all link types, except for the one that I need: WorkItemLinkTypes.CONTRIBUTES_TO_WORK_ITEM.

When trying to create a Contributes To / Tracks type link through above code, linking will work, but the work item that is the second parameter in the createLink(...) call will always show the dirty workitem banner in the Web UI ("Refresh to get the latest updates") as soon as the browser loses and regains focus, or the user changes the tab.
  
Again, this doesn't happen when using the same code, but WorkItemLinkTypes.PARENT_WORK_ITEM instead of WorkItemLinkTypes.CONTRIBUTES_TO_WORK_ITEM.

Changing above from effectively
linkServiceLibrary.createLink(WorkItemLinkTypes.CONTRIBUTES_TO_WORK_ITEM, child, parent);
to
linkServiceLibrary.createLink(WorkItemLinkTypes.TRACKS_WORK_ITEM, parent, child);
makes the problem appear in the parent work item instead of the child work item.

Can anyone please help me out?


Comments
Uwe Berthold commented Nov 28 '18, 8:58 a.m.

 A more readable version of above code, if I change it above, it gets flagged as spam for whatever reason.

// Create references
IReferenceFactory refFactory = IReferenceFactory.INSTANCE;
IReference parent = refFactory.createReferenceToItem(parentWI.getItemHandle());
IReference child = refFactory.createReferenceToItem(childWI.getItemHandle());
// Create the link
ILinkService linkService = getService(ILinkService.class);
ILinkServiceLibrary linkServiceLibrary = (ILinkServiceLibrary) linkService.getServiceLibrary(ILinkServiceLibrary.class);
ILink link = linkServiceLibrary.createLink(linkType, child, parent);
// Save the link
linkServiceLibrary.saveLink(link);

Accepted answer


permanent link
Uwe Berthold (6637) | answered Nov 30 '18, 1:24 a.m.
Thank you for your reply, Ralph. Re-reading the links provided, and further following links to enhancement requests in your blog finally gave me a working solution.

I'm sure others run into the same issues often, so I'm summarizing the key points that led me to the solution - they may be obvious for some, for me they were not:

  • CALM links may look similar to RTC links in the UI, they even share interfaces in the API, but require different methods to save successfully and without side-effects. 
    Furthermore, CALM links are not created bidirectionally by default, so you want to create two links; one on each work item involved.
  • Using ILinkServiceLibrary to create URI-based IReferences is a bad idea; if a link is created, it will either have "issues" (be undeletable, constantly reappearing "Refresh to get the latest changes" banner in one of the work items), or fail silently (no exception in linkServiceLibrary.saveLink(...), but no link either. Instead, use the second argument of IWorkItemServer.saveWorkItem3(...)
    [This may be a special case when one of the work items to be linked was just created, see https://rsjazz.wordpress.com/2013/11/06/creating-clm-links-with-back-link/]
What I had tried to "get there":
  1. Example code provided in the initial posting.
    Result: Successfully created a bidirectional link, but the work item that was the second argument would show a constantly reappearing "Refresh..." banner in the web UI only, even if no further changes were made (loss and re-gain of browser focus was enough to trigger it)
  2. Using ILinkServiceLibrary.createReferenceFromURI(...) instead of ILinkServiceLibrary.createReferenceToItem(...)
    --> Following the code provided in Ralph's answer, and creating a link that used IURIReferences instead of IItemReference, the call to ILinkServiceLibrary.saveLink(...) simply did nothing. No exception, but also no link. This may be because in my case, both items in the link had been created in the same operation participant call.
  3. Retrieving the tracked work item's references through IWorkItemServer.resolveWorkItemReferences(...) and adding a "contributes-to" link from the tracked to the tracking work item, using IURIReferences, then passing those references to the second parameter of IWorkItemServer.saveWorkItem3(...)
    --> A uni-directional, deletable link was created.
  4. Adding IAdditionalSaveParameters.UPDATE_BACKLINKS to the set of additional parameters in IWorkItemServer.saveWorkItem3(...) for the tracked work item
    --> Bi-directional links were created, but they were undeletable with nonsense error messages, e.g. claiming that the linked item does not exist (when removing the link). This was not related to permissions.
  5. Removing IAdditionalSaveParameters.UPDATE_BACKLINKS again, retrieving references for the tracking work item and explicitly creating the "back link" there, then saving that work item through IWorkItemServer.saveWorkItem3(...)
    --> The item was considered stale which threw an exception with a helpful error message.
  6. Explicitly retrieving an up-to-date copy of the work item through IWorkItemService.getAuditableCommon().resolveAuditable(...), retrieving its references, adding the new reference(s) (= backlinks) there and saving its working copy
    --> This finally worked
Ralph Schoon selected this answer as the correct answer

Comments
Ralph Schoon commented Nov 30 '18, 2:39 a.m.
FORUM ADMINISTRATOR / FORUM MODERATOR / JAZZ DEVELOPER

I think you should not have to create the back link. But I have not retried my code for some time and I am aware of some problems with backlinks when linking between RTC and other configuration enabled applications.


Anyway, thanks for the detailed response! 

2 other answers



permanent link
Ralph Schoon (63.6k33646) | answered Nov 28 '18, 10:37 a.m.
FORUM ADMINISTRATOR / FORUM MODERATOR / JAZZ DEVELOPER
edited Nov 28 '18, 11:06 a.m.

I think the issue is


createReferenceToItem
Different link types need to be treated differently. Tracks/contributes to is a CLM link type and does not link workitemhandles but URIs that you have to store.  

You want to use code similar to:
IWorkItem targetItem = (IWorkItem) iterator.next();
ILinkType tracksLinkType= ILinkTypeRegistry.INSTANCE.getLinkType(WorkItemLinkTypes.TRACKS_WORK_ITEM);
sourceReferences.add(tracksLinkType.getTargetEndPointDescriptor(), IReferenceFactory.INSTANCE.createReferenceFromURI(Location.namedLocation(targetItem, getPublicRepositoryURL()).toAbsoluteUri()));
The code above is from a follow up action I wrote that does what you try to do. I had the same issues, until I figured from looking at the link ends that the data created with the tool looked different than what I created. Note the code above uses some older API that you have already replaced. This should work with your code as well. The key here is the createReferenceFromURI and creating the location URI: Location.namedLocation(targetItem, getPublicRepositoryURL()).toAbsoluteUri()
IReferenceFactory.INSTANCE.createReferenceFromURI(Location.namedLocation(targetItem, getPublicRepositoryURL()).toAbsoluteUri())


Comments
1
Ralph Schoon commented Nov 28 '18, 10:52 a.m.
FORUM ADMINISTRATOR / FORUM MODERATOR / JAZZ DEVELOPER

Uwe Berthold commented Nov 30 '18, 1:05 a.m. | edited Nov 30 '18, 1:25 a.m.

[moved to answer]


permanent link
Uwe Berthold (6637) | answered Nov 30 '18, 1:59 a.m.
 // The parent work item that triggered the IOperationParticipant
IWorkItem parentWI = ...;
// Multiple work items created in the same call to this operation participant
List<IWorkItem> childWIs = ...;
// Work item server for retrieving and storing work item data
IWorkItemServer wiService = getService(IWorkItemServer.class);
// Link types for either direction
ILinkTypeServiceLibrary ltsl = (ILinkTypeServiceLibrary)getService(ILinkService.class).getServiceLibrary(ILinkTypeServiceLibrary.class);
ILinkType tracksLinkType = ltsl.getLinkType(WorkItemLinkTypes.TRACKS_WORK_ITEM);
ILinkType contributesToLinkType = ltsl.getLinkType(WorkItemLinkTypes.CONTRIBUTES_TO_WORK_ITEM);
List<IURIReference> childReferences = new ArrayList<>();
// -----------------------------------------------------------------------------
// 1. Create references from trackED work items to trackING work items
// -----------------------------------------------------------------------------
for (IWorkItem childWI : childWIs) {
    // Create and store a reference from the tracked work item to the tracking work item (contributes-to)
    Location parentLoc = Location.namedLocation(parentWI, getPublicRepositoryURL());
    IURIReference parentRef = IReferenceFactory.INSTANCE.createReferenceFromURI(parentLoc.toAbsoluteUri());
    IWorkItemReferences childRefs = wiService.resolveWorkItemReferences(childWI, monitor);
    childRefs.add(contributesToLinkType.getTargetEndPointDescriptor(), parentRef);
    // Save using a writable working copy
    wiService.saveWorkItem3((IWorkItem) childWI.getWorkingCopy(), childRefs, null, Collections.singleton(Constants.MY_EXTENSION_ID));
    // Create and remember a reference from the tracking work item to the tracked work item (tracks), and 
    // collect for storing in bulk later
    Location childLoc = Location.namedLocation(childWI, getPublicRepositoryURL());
    IURIReference childRef = IReferenceFactory.INSTANCE.createReferenceFromURI(childLoc.toAbsoluteUri());
    childReferences.add(childRef);
}
// -----------------------------------------------------------------------------
// 2. Create references from trackING work items to trackED work items
// -----------------------------------------------------------------------------
// Get an up-to-date instance of the parent work item (seems required, otherwise an exception is thrown: work item is stale)
ItemProfile<IAuditable> wiProfile = ItemProfile.createFullProfile(parentWI.getItemType());
IWorkItem parentWICurrent = (IWorkItem) wiService.getAuditableCommon().resolveAuditable(parentWI, wiProfile, monitor);
// Get its references, and add the previously created references to the list
IWorkItemReferences parentRefs = wiService.resolveWorkItemReferences(parentWICurrent, monitor);
for (IURIReference childRef : childReferences) {
    parentRefs.add(tracksLinkType.getTargetEndPointDescriptor(), childRef);
}
// Save it, using a writable working copy
wiService.saveWorkItem3((IWorkItem) parentWICurrent.getWorkingCopy(), parentRefs, null, Collections.singleton(Constants.MY_EXTENSION_ID));

Your answer


Register or to post your answer.


Dashboards and work items are no longer publicly available, so some links may be invalid. We now provide similar information through other means. Learn more here.