Use Server-Side Java API to Create/Modify Timesheet Entries (ITimeSheetEntry)
I have found several examples on these forums of how to programmatically create a Timesheet entry using the Java API. However, all of the examples that I've seen (including the Wiki) are for client-side java. I need to get this working for server-side Java, but have been unsuccessful.
It seems like I am almost there. When the code executes, it seems to create a phantom link to a timesheet that doesn't actually exist. After I visit a work item where this follow-up action was triggered, I get a java.lang.NullPointerException from the eclipse interface complaining that the target item doesn't exist. So it seems like the timesheet entry that I create, is destroyed or lost when the follow-up action terminates.
Here is the code:
public class Create_Timesheet_Entry_From_Comment extends AbstractService implements IOperationParticipant
{
boolean Debug = true;
@Override
public void run(AdvisableOperation operation, IProcessConfigurationElement participantConfig, IParticipantInfoCollector collector, IProgressMonitor monitor) throws TeamRepositoryException
{
Object data = operation.getOperationData();
SaveParameter saveParam = (SaveParameter)data;
//To avoid recursion, immediately exit if this participant was triggered by a previous execution of this participant
if(saveParam.getAdditionalSaveParameters().contains("CreateTimesheetEntryFromCommentOperationParticipantTriggeredThisSave"))
return;
IAuditable auditable = saveParam.getNewState();
if(!(auditable instanceof IWorkItem))
return;
if(Debug) System.out.println("The Create_Timesheet_Entry_From_Comment extension was triggered.");
IWorkItem thisWI = (IWorkItem)auditable;
IWorkItemServer service = getService(IWorkItemServer.class);
IWorkItem UpdateWorkItem = (IWorkItem) service.getAuditableCommon().resolveAuditable(thisWI, IWorkItem.FULL_PROFILE, null).getWorkingCopy();
//Examine the existing timesheet entries
IWorkItemReferences references = service.resolveWorkItemReferences(UpdateWorkItem, null);
ArrayList<IReference> ExistingTimeSheetEntries = new ArrayList<IReference>();
if(!references.getReferences(WorkItemEndPoints.WORK_TIME).isEmpty())
ExistingTimeSheetEntries.addAll(references.getReferences(WorkItemEndPoints.WORK_TIME));
IAuditableCommon iac = saveParam.getSaveOperationParameter().getAuditableCommon();
for(IReference thisEntry : ExistingTimeSheetEntries)
{
ITimeSheetEntry thisEntryItem = iac.resolveAuditable((ITimeSheetEntryHandle)thisEntry.resolve(), ITimeSheetEntry.FULL_PROFILE, null);
PrintTimesheetEntry(thisEntryItem);
if(Debug) return;
}
ITimeSheetEntry entry = service.createTimeSheetEntry(UpdateWorkItem.getProjectArea());
List<ITimeCode> timeCodes = service.getTimeCodes(UpdateWorkItem.getProjectArea());
ITimeCode timeCode = timeCodes.get(0);
Identifier<ILiteral> workType = Identifier.create(ILiteral.class, UpdateWorkItem.getWorkItemType());
Timestamp now = new Timestamp(System.currentTimeMillis());
entry.setStartDate(now);
entry.setTimeSpent(new Duration(2 * 60 * 60 * 1000));
entry.setTimeCodeId(timeCode.getTimeCodeId());
entry.setWorkType(workType);
entry.setTimeCode(timeCode.getTimeCodeLabel());
entry.setCreator(getAuthenticatedContributor());
entry.setRequestedStateId(UUID.generate());
if(Debug) {
System.out.println("The timesheet entry's settings are:");
PrintTimesheetEntry(entry);
}
/* //This method has never worked for any link type creation. Using the ILinkService has worked, but only for WorkItems
IWorkItemReferences workItemReferences = saveParam.getNewReferences();
if(Debug) System.out.println("Adding the newly created timesheet to the work item's references.");
workItemReferences.add(WorkItemEndPoints.WORK_TIME, IReferenceFactory.INSTANCE.createReferenceToItem(entry.getItemHandle()));
UpdateWorkItem.setRequestedModified(now);
Set<String> additionalParams = new HashSet<String>();
additionalParams.add("CreateTimesheetEntryFromCommentOperationParticipantTriggeredThisSave");
service.saveWorkItem3(UpdateWorkItem, null, null, additionalParams);*/
if(Debug) System.out.println("Attempting to create a link to the timesheet.");
LinkItemsWithoutPreconditions((IItem)UpdateWorkItem, (IItem)entry, WorkItemLinkTypes.TIME_SHEET_ENTRY);
LinkItemsWithoutPreconditions((IItem)entry, (IItem)UpdateWorkItem, WorkItemLinkTypes.TIME_SHEET_ENTRY_TARGET);
}
//NOTE: This method was given as an alternative, but I was cautioned against using it because it doesn't trigger operation behavior
//In my case, I think that's what I want.
void LinkItemsWithoutPreconditions(IItem sourceItem, IItem targetItem, String linkType) throws TeamRepositoryException
{
IReferenceFactory refFactory = IReferenceFactory.INSTANCE;
IReference source = refFactory.createReferenceToItem(sourceItem.getItemHandle());
IReference target = refFactory.createReferenceToItem(targetItem.getItemHandle());
if(source == null && Debug) System.out.println("The source is null.");
if(target == null && Debug) System.out.println("The target is null.");
if(source == null || target == null) return;
ILinkService linkService = getService(ILinkService.class);
ILinkServiceLibrary linkServiceLibrary = (ILinkServiceLibrary) linkService.getServiceLibrary(ILinkServiceLibrary.class);
ILink link = linkServiceLibrary.createLink(linkType, source, target);
linkServiceLibrary.saveLink(link);
}
public void PrintTimesheetEntry(ITimeSheetEntry entry)
{
System.out.println("\tStart Date = " + entry.getStartDate());
System.out.println("\tTimeCodeId = " + entry.getTimeCodeId());
System.out.println("\tHoursSpent = " + entry.getHoursSpent());
System.out.println("\tWorkType = " + entry.getWorkType());
System.out.println("\tTimeCode = " + entry.getTimeCode());
System.out.println("\tTimeSpent = " + entry.getTimeSpent());
System.out.println("\tCreator = " + entry.getCreator());
}}
There's a lot of code there, but I include it all for context. Here's the relevant part I think:
LinkItemsWithoutPreconditions((IItem)UpdateWorkItem, (IItem)entry, WorkItemLinkTypes.TIME_SHEET_ENTRY);
LinkItemsWithoutPreconditions((IItem)entry, (IItem)UpdateWorkItem, WorkItemLinkTypes.TIME_SHEET_ENTRY_TARGET);
This method has been successful for me in the past in linking work items. However, most of the examples that I've seen seem to suggest I should be using an approach like this:
IWorkItemReferences workItemReferences = saveParam.getNewReferences();
workItemReferences.add(WorkItemEndPoints.WORK_TIME, IReferenceFactory.INSTANCE.createReferenceToItem(entry.getItemHandle()));
service.saveWorkItem3(UpdateWorkItem, null, null, additionalParams);
I haven't used this second approach because I've never been able to get it to work for saving links between work items. I've tried it here for Timesheet entries anyway, but it didn't seem to work here either.
The examples that I've seen seem to emphasize the importance of a command like:
workingCopy.getDependentItems().add(entry);
However, that command doesn't seem to be available in the same way in a server-side extension.
I'd appreciate any help anyone can offer.
One answer
In the code above, if you create a reference, you have to add the reference to saveWorItem3 as a parameter to get them saved.
IStatus saveStatus = fWorkItemServer.saveWorkItem3(workItem, references, workFlowAction, additionalParams);
Comments
I've tried multiple permutations of the above code and including the references in the save operation was one of them. That didn't seem to do the trick. I elaborate on the other post to which you linked in your response.
Note that I think the approach above doesn't technically even need a workitem operation save. The helper function "LinkItemsWithoutPreconditions" saves the links without touching the work items. I've used it before to create links without triggering a Workitem save operation. I actually found it from one of your blog posts. You advised against using it because it didn't trigger an operation behavior, but in my case that's actually what I wanted. Even though it wasn't needed, I tried it anyway with and without the references.
Nate,
my comment above holds only true to saving the reference. If there is already a reference, you can't just create another one.
For update - in the client API - the key was:
If the timesheet for that day and work code does not exist, create a new timesheet, create the relationship, add the timesheet to the dependent items and then save; the dependent Item together with the required update to the item and the relationship makes sure all is saved.
If the timesheet for that day and work code does exist, update the time sheet and update the work item time spent, add the timesheet to the dependent items; this makes sure the work item and the dependent item is created.
The dependent Item does not exist for server based operations, since the WorkingCopy used in the client does not exist.
You can, I believe, use the ILinkService to create links between items without having to save the items. Trouble is, that also does not surface in operational behavior.
I don't see where you save the ITimesheet entry, so the link would be lost
Sam and Ralph, that's exactly my problem. The timesheet is created in memory, but never saved. In the client examples, the save happens by extension of saving the work item because the timesheet is added to the work item's list of dependent items. However, in the server-side API there doesn't seem to be an equivalent mechanism where you can flag the Timesheet Entry as a dependency.
If that mechanism doesn't exist, I need to be able to explicitly save the timesheet entry some other way. I've been unable to find any save mechanism though.
This is a frustrating problem because it seems really counter-intuitive that you could do something with a client API that isn't possible in a server API. I would think the server-API would be the more powerful and capable mechanism.
looking at the code for WorkItemWorkingCopyManager in the sdk,
this devolves down to
IProcessClientService processClient= (IProcessClientService) fTeamRepository.getClientLibrary(IProcessClientService.class);
WorkItemSaveRunnable saveRunnable= new WorkItemSaveRunnable(this, multiSaveParameter, transport);
try {
processClient.execute(saveRunnable, operationName, monitor);
so I think thi should work.. save timesheet entry separately. fWorkItemServer.getAuditableCommon().saveProcessItem(processItem, monitor);