// UC 6: Create a TR based on a Bug. // Return if the bug is updated or not. private void createTR( Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem workItem, WorkItemChangedEvent notification, String user, ref Status status) { // Create the ECR callCreateTR(ECRMapper.mapFromWorkitem(workItem, null, ECRMapper.ACTION_CREATE), user, ref status); if (!status.OK) { return; } // UC 3 (one case): Update all changed attributes before handling any state change // Note: If adding an entry in History on create this should propagate to Progress Info, but // this caused Bug to appear dirty after Save, see issue 79. Given almost all fields are used on // create, it does not make sense to make extra update as below. TBD if we should re-add in a // changed form. //EnterpriseChangeRequest updatedEcr = ECRMapper.mapFromUpdatedWorkitem(workItem, status.TRAbout, notification); //if (updatedEcr != null) //{ // // Note: Failure to update will be Warning not Error, so status still OK. // callUpdateTR(updatedEcr, user, null, ref status); //} // Set the ECR in state Registered EnterpriseChangeRequest ecr = ECRMapper.mapFromWorkitem(workItem, status.TRAbout, ECRMapper.ACTION_REGISTER_ROUTE); callUpdateTR(ecr, user, ECRMapper.TR_STATE_REGISTERED_S, ref status); if (!status.OK) { return; } // Set the ECR in state Assigned if we have a defined owner ecr = ECRMapper.mapFromWorkitem(workItem, status.TRAbout, ECRMapper.ACTION_ASSIGN); if (ecr.GetOwner() != null && ecr.GetOwner().Length > 0) { callUpdateTR(ecr, user, ECRMapper.TR_STATE_ASSIGNED_S, ref status); if (!status.OK) { return; } } if (status.OK) { HandlerSettings.LogMessage( String.Format("Created TR based on workitem named: {0}", workItem.Title), HandlerSettings.LoggingLevel.INFO); } }
// Update the changed fields that are mapped. Return if the bug is updated or not. private void updateTRFields( Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem workItem, WorkItemChangedEvent notification, String user, Uri about, ref Status status) { // UC 3 (one case): Update all changed attributes before handling any state change. EnterpriseChangeRequest updatedEcr = ECRMapper.mapFromUpdatedWorkitem(workItem, about, notification); if (updatedEcr != null) { // Note: Failure to update will be Warning not Error, so status still OK. callUpdateTR(updatedEcr, user, null, ref status); } }
// Return if the workitem is updated (saved) by the handling code. public void handleEvent( Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem workItem, String user, WorkItemChangedEvent notification) { Status status = new Status(); // If the user is the OSLC Provider functional user we will only update status // message if so needed. This as changes from the OSLC Provider originates from // external source = currently: MHWeb. Need revisit to allow multiple clients. String aboutStr = AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_ABOUT, workItem); if (!user.Equals(HandlerSettings.TFSProviderUser, StringComparison.OrdinalIgnoreCase)) { if (aboutStr.Length == 0) { // We do not have a linked TR, we need to create createTR(workItem, notification, user, ref status); } else { Uri about = new Uri(aboutStr); Status statusAssign = null; EnterpriseChangeRequest ecr = ECRMapper.mapFromWorkitem(workItem, about, ECRMapper.ACTION_ASSIGN); TeamFoundationIdentity assignedTo = HandlerSettings.GetSignumForAssignedTo(workItem); if (user != null && assignedTo != null && !user.Equals(HandlerSettings.GetUserFromSignum(assignedTo.UniqueName), StringComparison.OrdinalIgnoreCase)) { statusAssign = new Status(); ecr.SetOwner(user); callUpdateTR(ecr, user, ECRMapper.TR_STATE_ASSIGNED_S, ref statusAssign); } // We have a TR linked which might need to be updated updateTR(workItem, notification, user, about, ref status); if (statusAssign != null && statusAssign.OK) { ecr.SetOwner(HandlerSettings.GetUserFromSignum(assignedTo.UniqueName)); callUpdateTR(ecr, user, ECRMapper.TR_STATE_ASSIGNED_S, ref statusAssign); } } } // Handle update of Bug ECRMapper.updateBug(status, user, workItem); }
// ========================================================= // Handle the WorkItemChangedEvent event // Disconnect TR if conditions are matching. Return if workitem has been saved. public void handleDisconnectEvent( Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem workItem, String user, WorkItemChangedEvent notification) { Uri about = ECRMapper.getDisconnectedTRLink(workItem, notification); if (about == null) { return; } EnterpriseChangeRequest ecr = ECRMapper.mapFromWorkitem( workItem, about, ECRMapper.ACTION_DISCONNECT); Status status = new Status(); callUpdateTR(ecr, user, null, ref status); if (!status.OK) { HandlerSettings.LogMessage( "Failed to disconnect TR from Bug.", HandlerSettings.LoggingLevel.WARN); } }
public EventNotificationStatus ProcessEvent(TeamFoundationRequestContext requestContext, NotificationType notificationType, object notificationEventArgs, out int statusCode, out string statusMessage, out ExceptionPropertyCollection properties) { statusCode = 0; properties = null; statusMessage = String.Empty; // TODO: Make impact on performance minimal by optimize logic for understanding if we are interested // in this event or not. Also put longer running task in Jobs. For now - do basic checks. if (notificationType != NotificationType.Notification) { return(EventNotificationStatus.ActionPermitted); } var notification = notificationEventArgs as WorkItemChangedEvent; if (notification == null) { return(EventNotificationStatus.ActionPermitted); } // Should not thow exception - if so, the code will be disabled by TFS. try { // Init adapter - will only be done once per deployment of plugin if (!HandlerSettings.initFromFile()) { // Fatal error when trying to init - return // TODO: How allow retry without server restart or re-deploy of plugin? return(EventNotificationStatus.ActionPermitted); } // Handle case where event is fired because of Save we initiated. if (ignoreSaveEvent(notification)) { return(EventNotificationStatus.ActionPermitted); } int workItemId = notification.CoreFields.IntegerFields[0].NewValue; HandlerSettings.LogMessage( String.Format( "WorkItem with id {0} named '{1}' was saved.", "" + workItemId, notification.WorkItemTitle), HandlerSettings.LoggingLevel.INFO); // TODO: In examples I have seen you get actual WI by process below, i.e. opening a new connection to TFS. // I would expect as we already are in the context of a TFS you somehow could use this. Note: There is a // WorkItem class in the namespace Microsoft.TeamFoundation.WorkItemTracking.Server - no documentation found. Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem workItem = getStore().GetWorkItem(workItemId); // If a new item is based on an existing (copied) we need to reset the connection to any existing TR. // Note exception if item is created by the functional user. This to allow test case where TR is set // by test case code. String user = HandlerSettings.GetSignumForChangeNotification(workItem, notification); if (notification.ChangeType == ChangeTypes.New) { if (!user.Equals(HandlerSettings.TFSProviderUser, StringComparison.OrdinalIgnoreCase)) { // Note: Will not save - done in event handling or at end. ECRMapper.disconnectBug(workItem); } } ProductMapper.EventType eventType = ProductMapper.getInstance().GetEventType(workItem, notification, user); if (eventType == ProductMapper.EventType.CreateUpdate) { // Handle the event as the release is for a maintenance product RESTCallHandler.getHandler().handleEvent(workItem, user, notification); } else if (eventType == ProductMapper.EventType.Disconnect) { // Handle the event as the release is for a maintenance product RESTCallHandler.getHandler().handleDisconnectEvent(workItem, user, notification); } else { HandlerSettings.LogMessage( String.Format( "WorkItem with id {0} save event was ignored by integration.", "" + workItemId), HandlerSettings.LoggingLevel.INFO); } // If updated but not saved, we need to explicitly call save. Very unusual case but will happen // e.g. if item is copied with a product value that before was in maintenance but now not. Then // code to disconnect bug is called, but event not handled or Bug saved, hence save here. if (workItem.IsDirty) { RESTCallHandler.getHandler().saveBug(workItem); } } catch (Exception ex) { // Error when reading mapping file - assume fatal HandlerSettings.LogMessage( "Error when handling the change of workitem." + "\nError: " + ex.Message + "\nStack: " + ex.StackTrace, HandlerSettings.LoggingLevel.ERROR); } return(EventNotificationStatus.ActionPermitted); }
// Update the TR State field for the bug based either on incoming ecr, or if this is null // get the current TR State from the bug. If missing link for a maintenance product or if // having a link for a non-maintenance product, also show this. static public void updateBug(Status status, String user, Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem workItem) { // Check if we are connected when we should be bool isProdInMaint = ProductMapper.getInstance().IsProductInMaintenance(workItem); String currentLink = HandlerSettings.GetIDFromLink(workItem); bool hasLink = currentLink.Length > 0; if (isProdInMaint && (!hasLink && status.TRAbout == null)) { // Error - should have link and nothing in the status.TRAbout so have not created. workItem.Fields[TFSMapper.ERICSSON_DEFECT_STATE_FIELD].Value = trStateToString[TRStateInfo.ErrorMissingTR]; } else if (!isProdInMaint) { // OK - clear any status fields and return ECRMapper.disconnectBug(workItem); return; } else { // OK - prod is in maint and has link - normal case // Check if state is updated String newTrState = null; if (status.TRState != null) { String currentTrState = getTRState(workItem); String updatedTrState = status.TRState; if (!updatedTrState.Equals(currentTrState)) { newTrState = updatedTrState; workItem.Fields[TFSMapper.ERICSSON_DEFECT_STATE_FIELD].Value = newTrState; } } if (status.TRAbout != null) { // We will store the relative path of the "Edit TR" link in the bug, reason being that the Link // that we need to define for the UI to allow click to navigate require a non blank UriRoot value, // see http://msdn.microsoft.com/en-us/library/dd936107.aspx. String updatedLink = HandlerSettings.GetIDFromUri(status.TRAbout.ToString()); if (updatedLink.Length > 0) { if (!currentLink.Equals(updatedLink)) { workItem.Fields[TFSMapper.ERICSSON_DEFECT_LINK_FIELD].Value = updatedLink; } } } } // Put the error and expected state string into system history field String message = status.GetMessage(); // Append any defined guiding message TRSyncState newSyncState = getSyncState(status, workItem); String guidingMessage = ""; switch (newSyncState) { case TRSyncState.SyncStateNone: guidingMessage = HandlerSettings.SyncNoSyncMessage; break; case TRSyncState.SyncStateOK: guidingMessage = HandlerSettings.SyncSuccessMessage; break; case TRSyncState.SyncStateWarning: guidingMessage = HandlerSettings.SyncWarningMessage; break; case TRSyncState.SyncStateError: guidingMessage = HandlerSettings.SyncErrorMessage; break; } if (message.Length > 0 || guidingMessage.Length > 0) { workItem.History = message + ((guidingMessage.Length > 0) ? " " + guidingMessage : ""); } // Put the sync state in the Sync State field String currentSyncStateStr = workItem.Fields[TFSMapper.ERICSSON_DEFECT_SYNCSTATE].Value.ToString(); String newSyncStateStr = trSyncStateToString[newSyncState]; bool incomingChange = user.Equals(HandlerSettings.TFSProviderUser, StringComparison.OrdinalIgnoreCase); newSyncStateStr = String.Format(newSyncStateStr, incomingChange ? "(in)" : "(out)"); if (!currentSyncStateStr.Equals(trSyncStateToString)) { workItem.Fields[TFSMapper.ERICSSON_DEFECT_SYNCSTATE].Value = newSyncStateStr; } }
// ======================================================================= // Mapping from workItem to ECR // Return the Bug and TR state change if part of update public static void getStatusChange(WorkItemChangedEvent notification, Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem workItem, out String oldState, out String newState, out String action) { newState = null; oldState = null; action = null; // If the Bug and TR are in different states, block update from Bug to TR. if (ECRMapper.IsSyncError(workItem)) { action = ACTION_IGNORE; return; } bool changedAssignedTo = false; // If Bug marked as Duplicate, the only activity to be propagated to TR is Unduplicate. String substate = workItem.Fields[TFSMapper.TFS_SUBSTATE].Value.ToString(); bool isDuplicate = substate.Equals(TFSMapper.TFS_SUBSTATE_DUPLICATE); bool isDuplicateChanged = false; if (notification.ChangedFields == null || notification.ChangedFields.StringFields == null) { // No fields of interest are changed return; } StringField[] changedFields = notification.ChangedFields.StringFields; for (int i = 0; i < changedFields.Length; i++) { String name = changedFields[i].ReferenceName; if (name.Equals(TFSMapper.TFS_STATE, StringComparison.OrdinalIgnoreCase)) { oldState = changedFields[i].OldValue; newState = changedFields[i].NewValue; } else if (name.Equals(TFSMapper.TFS_OWNER, StringComparison.OrdinalIgnoreCase)) { changedAssignedTo = true; } // If TFS_SUBSTATE is changed to/from Duplicate the TR should be set to // corresponding state. If marked as Duplicate, the only activity that // should be propagated to TR is Unduplicate. else if (name.Equals(TFSMapper.TFS_SUBSTATE)) { if (changedFields[i].NewValue.Equals(TFSMapper.TFS_SUBSTATE_DUPLICATE) || changedFields[i].OldValue.Equals(TFSMapper.TFS_SUBSTATE_DUPLICATE)) { isDuplicateChanged = true; } } // If the release is changed, we need to update the TR product // Note: In case we also set to Duplicate, we will disregard product change else if (name.Equals(TFSMapper.TFS_FAULTY_PRODUCT)) { action = ACTION_CHANGE_PRODUCT; } } if (isDuplicateChanged) { // If the Duplicate flag is changed we need to act on this. This is a substate flag, so // it can happen also during a state transition. action = isDuplicate ? ACTION_DUPLICATE : ACTION_UNDUPLICATE; return; } else if (isDuplicate) { // If Bug is duplicate and there is no change for this state, we should send no // updates to TR until unduplicated. action = ACTION_IGNORE; return; } // If we have a changed of "Assigned To" this will only be possible to update in specific // TR states. So if Assigned To changed at same time as state - likely need to prevent. if (changedAssignedTo) { if (newState == null || oldState == null) { // No state change for Bug, but for TR oldState = BUG_STATE_ACTIVE; newState = BUG_STATE_ACTIVE; } else { HandlerSettings.LogMessage( String.Format( "The 'Assigned To' change can not be done for TR: {0} at same time as state change", workItem.Title), HandlerSettings.LoggingLevel.WARN); } } }
public void callUpdateTR(EnterpriseChangeRequest ecr, String user, String expectedState, ref Status status) { String message = null; HttpResponseMessage updateResponse = null; try { String about = ecr.GetAbout().ToString(); updateResponse = getOslcClient().UpdateResource( about, ecr, OslcMediaType.APPLICATION_RDF_XML, OslcMediaType.APPLICATION_RDF_XML, HandlerSettings.getRESTHeaders(user)); message = (updateResponse != null) ? updateResponse.Content.ReadAsStringAsync().Result : "No result when calling " + about; } catch (Exception ex) { HandlerSettings.LogMessage( String.Format("Failed to update ECR: '{0}'", ex.Message), HandlerSettings.LoggingLevel.ERROR); status.ErrorMessage = "Failed to update TR from Bug. Error: '" + ex.Message + "'"; return; } if (updateResponse == null || updateResponse.StatusCode != HttpStatusCode.OK) { HandlerSettings.LogMessage( String.Format("Failed to update ECR. Error from MHWeb: '{0}'", message), HandlerSettings.LoggingLevel.ERROR); // If trying a state change, report as error - otherwise as warning if (expectedState != null) { status.ErrorMessage = "Failed to update TR from Bug. Error from MHWeb: '" + message + "'"; } else { status.WarningMessage = "Warnings when update TR from Bug. Message from MHWeb: '" + message + "'"; } return; } else { // Log the incoming rdf HandlerSettings.LogMessage( String.Format("Response from update:\n{0}", message), HandlerSettings.LoggingLevel.INFO); } // NOTE: MHWeb return an updated item. This is convenient, but not by spec. // Might need an explicit GET here to make more robust if other clients. EnterpriseChangeRequest newEcr = updateResponse.Content. ReadAsAsync <EnterpriseChangeRequest>(getOslcClient().GetFormatters()).Result; String shortState = newEcr.GetStatus(); status.TRState = ECRMapper.getLongTRState(shortState); if (expectedState != null) { // Verify result if we did a state change if (shortState == null || shortState != expectedState) { status.ExpectedStateMessage = String.Format("The ECR is in wrong state: {0} expected: {1}", shortState, expectedState); HandlerSettings.LogMessage(status.ExpectedStateMessage, HandlerSettings.LoggingLevel.WARN); } HandlerSettings.LogMessage( String.Format("The ECR named {0} is updated to state: {1}", newEcr.GetTitle(), status.TRState), HandlerSettings.LoggingLevel.INFO); } else { HandlerSettings.LogMessage( String.Format("The ECR named: {0} is updated", newEcr.GetTitle()), HandlerSettings.LoggingLevel.INFO); } return; }
// ====================================================================================== // REST calls to create or update public void callCreateTR(EnterpriseChangeRequest ecr, String user, ref Status status) { HttpResponseMessage creationResponse = null; String message; try { creationResponse = getOslcClient().CreateResource( HandlerSettings.CreationFactoryUri, ecr, OslcMediaType.APPLICATION_RDF_XML, OslcMediaType.APPLICATION_RDF_XML, HandlerSettings.getRESTHeaders(user)); message = (creationResponse != null) ? creationResponse.Content.ReadAsStringAsync().Result : "No result when calling " + HandlerSettings.CreationFactoryUri; } catch (Exception ex) { HandlerSettings.LogMessage( String.Format("Failed to create ECR: '{0}'", ex.Message), HandlerSettings.LoggingLevel.ERROR); status.ErrorMessage = "Failed to create TR from Bug. Error: '" + ex.Message + "'"; return; } if (creationResponse == null || creationResponse.StatusCode != HttpStatusCode.Created) { HandlerSettings.LogMessage( String.Format("Failed to create ECR. Error from MHWeb: '{0}'", message), HandlerSettings.LoggingLevel.ERROR); status.ErrorMessage = "Failed to create TR from Bug. Error from MHWeb: '" + message + "'"; return; } else { // Log the incoming rdf HandlerSettings.LogMessage( String.Format("Response from create:\n{0}", message), HandlerSettings.LoggingLevel.INFO); } EnterpriseChangeRequest newEcr = creationResponse.Content. ReadAsAsync <EnterpriseChangeRequest>(getOslcClient().GetFormatters()).Result; status.TRAbout = newEcr.GetAbout(); String shortState = newEcr.GetStatus(); status.TRState = ECRMapper.getLongTRState(shortState); HandlerSettings.LogMessage( String.Format("ECR named {0} created a location: {1}", newEcr.GetTitle(), status.TRAbout), HandlerSettings.LoggingLevel.INFO); // Verify result if (shortState == null || shortState != ECRMapper.TR_STATE_PRIVATE_S) { status.ExpectedStateMessage = String.Format("Created ECR in wrong state: {0} expected: PR", shortState); HandlerSettings.LogMessage(status.ExpectedStateMessage, HandlerSettings.LoggingLevel.WARN); } return; }
// Update the connected TR State. Return if the bug is updated or not. private void updateTRState( Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem workItem, String oldState, String newState, String user, Uri about, ref Status status) { status.TRState = ECRMapper.getTRState(workItem); if (oldState.Equals(ECRMapper.BUG_STATE_ACTIVE, StringComparison.OrdinalIgnoreCase) && newState.Equals(ECRMapper.BUG_STATE_RESOLVED, StringComparison.OrdinalIgnoreCase)) { // UC 4: Updated TR based on the Bug's Active -> Resolve state change EnterpriseChangeRequest ecr = null; if (status.TRState.Equals(ECRMapper.TR_STATE_ASSIGNED)) { ecr = ECRMapper.mapFromWorkitem(workItem, about, ECRMapper.ACTION_PROPOSE); callUpdateTR(ecr, user, ECRMapper.TR_STATE_PROPOSED_S, ref status); if (!status.OK) { return; } } if (status.TRState.Equals(ECRMapper.TR_STATE_PROPOSED)) { String answerCode = ""; if (ecr != null) { answerCode = ecr.GetAnswerCode(); } else { answerCode = AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_ANSWER_CODE, workItem); } if (answerCode.Contains("A") || answerCode.Contains("D") || answerCode.Contains("B11")) { // In case Answer Code = A*, D*, or B11 go directly to Answer callUpdateTR(ECRMapper.mapFromWorkitem(workItem, about, ECRMapper.ACTION_ANSWER), user, ECRMapper.TR_STATE_TECH_ANSW_PROV_S, ref status); return; } else { callUpdateTR(ECRMapper.mapFromWorkitem(workItem, about, ECRMapper.ACTION_APPROVE), user, ECRMapper.TR_STATE_PROP_APPROV_S, ref status); } } String expectedState = ECRMapper.TR_STATE_PROP_APPROV; if (!status.TRState.Equals(expectedState)) { // Incorrect pre condition HandlerSettings.LogMessage( String.Format("Expected TR State: {0}, current TR state: {1}", expectedState, status.TRState), HandlerSettings.LoggingLevel.WARN); } // Note: Here we assume we are in trState PA, as we accepted AS, PP and PA. If we have not failed // before, this is where we will fail if any assumption was wrong -> error message. if (status.OK) { callUpdateTR(ECRMapper.mapFromWorkitem(workItem, about, ECRMapper.ACTION_VERIFY), user, ECRMapper.TR_STATE_TECH_ANSW_PROV_S, ref status); } } else if (oldState.Equals(ECRMapper.BUG_STATE_RESOLVED, StringComparison.OrdinalIgnoreCase) && newState.Equals(ECRMapper.BUG_STATE_CLOSED, StringComparison.OrdinalIgnoreCase)) { // Update a TR based on Close of the Bug's Resolve -> Close state change String expectedState = ECRMapper.TR_STATE_TECH_ANSW_PROV; if (!status.TRState.Equals(expectedState)) { // Incorrect pre condition HandlerSettings.LogMessage( String.Format("Expected TR State: {0}, current TR state: {1}", expectedState, status.TRState), HandlerSettings.LoggingLevel.WARN); } // TODO: Put the condition in configuration file etc. String answerCode = AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_ANSWER_CODE, workItem); if (answerCode.Equals("D4")) { // UC 9.3 - Bug set as Postponed callUpdateTR(ECRMapper.mapFromWorkitem(workItem, about, ECRMapper.ACTION_ACCEPT), user, ECRMapper.TR_STATE_POSTPONED_S, ref status); } else if (answerCode.Equals("Duplicate")) { // UC 9.4 - Bug set as Duplicate callUpdateTR(ECRMapper.mapFromWorkitem(workItem, about, ECRMapper.ACTION_DUPLICATE), user, null, ref status); } else { // UC 9.1 - Close of Bug from TFS callUpdateTR(ECRMapper.mapFromWorkitem(workItem, about, ECRMapper.ACTION_FINISH), user, ECRMapper.TR_STATE_FINISHED_S, ref status); } } else if (oldState.Equals(ECRMapper.BUG_STATE_RESOLVED, StringComparison.OrdinalIgnoreCase) && newState.Equals(ECRMapper.BUG_STATE_ACTIVE, StringComparison.OrdinalIgnoreCase)) { // UC 9.2: Update a TR based the Bug's Resolved -> Active state change callUpdateTR(ECRMapper.mapFromWorkitem(workItem, about, ECRMapper.ACTION_REJECT), user, ECRMapper.TR_STATE_REGISTERED_S, ref status); if (!status.OK) { return; } // If Bug is assigned to a user we need to drive change back to Assigned state EnterpriseChangeRequest ecr = ECRMapper.mapFromWorkitem(workItem, about, ECRMapper.ACTION_ASSIGN); if (ecr.GetOwner() != null && ecr.GetOwner().Length > 0) { callUpdateTR(ecr, user, ECRMapper.TR_STATE_ASSIGNED_S, ref status); } } else if (oldState.Equals(ECRMapper.BUG_STATE_CLOSED, StringComparison.OrdinalIgnoreCase) && newState.Equals(ECRMapper.BUG_STATE_ACTIVE, StringComparison.OrdinalIgnoreCase)) { // UC 9.2: Update a TR based the Bug's Closed -> Active state change // In case of Closed -> Active, this is only allowed for the "no action" answer codes // in MHWeb, so OK for this to fail if selecting the incorrect answer code. // Could block in Bug.xml if we want to prevent some cases. callUpdateTR(ECRMapper.mapFromWorkitem(workItem, about, ECRMapper.ACTION_REACTIVATE), user, ECRMapper.TR_STATE_REGISTERED_S, ref status); if (!status.OK) { return; } // If Bug is assigned to a user we need to drive change back to Assigned state EnterpriseChangeRequest ecr = ECRMapper.mapFromWorkitem(workItem, about, ECRMapper.ACTION_ASSIGN); if (ecr.GetOwner() != null && ecr.GetOwner().Length > 0) { callUpdateTR(ecr, user, ECRMapper.TR_STATE_ASSIGNED_S, ref status); } } else if (oldState.Equals(ECRMapper.BUG_STATE_ACTIVE, StringComparison.OrdinalIgnoreCase) && newState.Equals(ECRMapper.BUG_STATE_ACTIVE, StringComparison.OrdinalIgnoreCase)) { // UC 3 (one case): Handle Assign case - state change for TR, attribute change for Bug // Incorrect pre condition String expectedState = ECRMapper.TR_STATE_REGISTERED; if (!status.TRState.Equals(expectedState)) { HandlerSettings.LogMessage( String.Format("Expected TR State: {0}, current TR state: {1}", expectedState, status.TRState), HandlerSettings.LoggingLevel.WARN); } // Handle case when we are in state PR if (status.TRState.Equals(ECRMapper.TR_STATE_PRIVATE)) { callUpdateTR(ECRMapper.mapFromWorkitem( workItem, about, ECRMapper.ACTION_REGISTER_ROUTE), user, ECRMapper.TR_STATE_REGISTERED_S, ref status); } callUpdateTR(ECRMapper.mapFromWorkitem( workItem, about, ECRMapper.ACTION_ASSIGN), user, ECRMapper.TR_STATE_ASSIGNED_S, ref status); } }
// Update the connected TR. private void updateTR( Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem workItem, WorkItemChangedEvent notification, String user, Uri about, ref Status status) { String oldState, newState, action; ECRMapper.getStatusChange(notification, workItem, out oldState, out newState, out action); if (action != null) { if (action.Equals(ECRMapper.ACTION_IGNORE)) { // Ignore all changes return; } if (action.Equals(ECRMapper.ACTION_DUPLICATE)) { // UC 9.4: Duplicate - Find Bug with duplicate id, get attached TR and send action duplicate // Update needs to be done before setting the Bug as Duplicate as the TR can't change after. updateTRFields(workItem, notification, user, about, ref status); EnterpriseChangeRequest ecr = ECRMapper.mapFromWorkitem(workItem, about, ECRMapper.ACTION_DUPLICATE); if (ecr.GetPrimaryTR() == null || ecr.GetPrimaryTR().Length == 0) { // Failed to find a Bug with a connected TR. Log error and return. String msg = String.Format( "Failed to find duplicate to Bug: {0}, or duplicate Bug had not TR Link set", workItem.Title); HandlerSettings.LogMessage(msg, HandlerSettings.LoggingLevel.ERROR); status.ErrorMessage = msg; return; } callUpdateTR(ecr, user, null, ref status); if (!status.OK) { // Log issue, but continue if e.g. a state change HandlerSettings.LogMessage( String.Format("Failed to set TR as duplicate based on Bug: {0}", workItem.Title), HandlerSettings.LoggingLevel.WARN); } // After setting Bug/TR as Duplicated, no futher changes should be propagated. return; } if (action.Equals(ECRMapper.ACTION_UNDUPLICATE)) { // UC 9.4: Unduplicate - Move TR back to Registered by sending action unduplicate EnterpriseChangeRequest ecr = ECRMapper.mapFromWorkitem(workItem, about, ECRMapper.ACTION_UNDUPLICATE); callUpdateTR(ecr, user, null, ref status); if (!status.OK) { // Log issue, but continue if e.g. a state change HandlerSettings.LogMessage( String.Format("Failed to unduplicate TR based on Bug ", workItem.Title), HandlerSettings.LoggingLevel.WARN); } // Update needs to be done after "Unduplicating" the Bug as the TR can't change before. updateTRFields(workItem, notification, user, about, ref status); // Return before handling a possible state change as states can be out of sync // (normal case) when Bug has been Duplicate. Bug then Resolved / Closed, and // TR in an Active state. User needs to set TR state to Active. return; } else if (action.Equals(ECRMapper.ACTION_CHANGE_PRODUCT)) { // UC 10.2: Update of the release - propagate info to TR EnterpriseChangeRequest ecr = ECRMapper.mapFromWorkitem(workItem, about, ECRMapper.ACTION_CHANGE_PRODUCT); bool noProduct = ecr.GetProduct().Length == 0; if (noProduct) { // No product found, so value is for internal release - add message in notebook ecr.SetNotebook("The referenced design item was moved to a product internal to Design"); } callUpdateTR(ecr, user, null, ref status); if (!status.OK) { // Log issue, but continue if e.g. a state change HandlerSettings.LogMessage( String.Format("Failed to change product on TR based on Bug ", workItem.Title), HandlerSettings.LoggingLevel.WARN); } } } updateTRFields(workItem, notification, user, about, ref status); if (newState != null) { // Handle state change updateTRState(workItem, oldState, newState, user, about, ref status);; } }