// If set, clear the TR fields conneting the Bug with a TR. static public Boolean disconnectBug(Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem workItem) { Boolean updated = false; if (workItem.Fields[TFSMapper.ERICSSON_DEFECT_LINK_FIELD].Value != null && workItem.Fields[TFSMapper.ERICSSON_DEFECT_LINK_FIELD].Value.ToString().Length > 0) { String trId = workItem.Fields[TFSMapper.ERICSSON_DEFECT_LINK_FIELD].Value.ToString(); workItem.Fields[TFSMapper.TFS_HISTORY].Value = "Workitem disconnected from TR: " + trId; workItem.Fields[TFSMapper.ERICSSON_DEFECT_LINK_FIELD].Value = null; workItem.Fields[TFSMapper.ERICSSON_DEFECT_STATE_FIELD].Value = null; workItem.Fields[TFSMapper.ERICSSON_DEFECT_SYNCSTATE].Value = null; updated = true; HandlerSettings.LogMessage( String.Format( "Cleared TR connection fields for WorkItem with id {0}.", "" + workItem.Id), HandlerSettings.LoggingLevel.INFO); } return(updated); }
// Return if Bug is successfully saved or not. Note that Save will cause event that will be // triggering our code - but correctly filtered out as done by the functional user. public Boolean saveBug( Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem workItem) { try { workItem.Save(); HandlerSettings.LogMessage( String.Format("Saved workitem: {0}", workItem.Title), HandlerSettings.LoggingLevel.INFO); } catch (Exception e) { // According to doc there are two exceptions that can be thrown: // Microsoft.TeamFoundation.WorkItemTracking.Client.ValidationException // Microsoft.TeamFoundation.WorkItemTracking.Client.DeniedOrNotExistException // but at least ValidationException is not recognized as subclass to Exception so compile error // See http://msdn.microsoft.com/en-us/library/microsoft.teamfoundation.workitemtracking.client.workitem.partialopen.aspx HandlerSettings.LogMessage( String.Format("Failed to save workitem, error: {0}", e.Message), HandlerSettings.LoggingLevel.ERROR); return(false); } return(true); }
// Get the Uri for the TR before disconnected assuming disconnect was done in // previous save. Preconditions should be checked by caller. static public Uri getDisconnectedTRLink( Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem workItem, WorkItemChangedEvent notification) { // Apparently we need to check on second last revision to get the correct // value for the previous value. This does not show in UI, but is clear when // debugging. So check we have > 2 revisions, and then compare. if (workItem.Revision < 3) { return(null); } Revision lastRev = workItem.Revisions[workItem.Revision - 2]; String oldTrLink = lastRev.Fields[TFSMapper.ERICSSON_DEFECT_LINK_FIELD].Value.ToString(); if (oldTrLink == null || oldTrLink.Length == 0) { return(null); } HandlerSettings.LogMessage( String.Format( "Disconnected TR '{0}' from WorkItem with id {1}.", oldTrLink, "" + workItem.Id), HandlerSettings.LoggingLevel.INFO); return(new Uri(HandlerSettings.GetUriForTR(oldTrLink))); }
// Return an authenticated oslc client. Throws exception in case not found. public OslcClient2 getOslcClient() { if (oslcClient != null) { return(oslcClient); } // Get the Friend info for mhweb FriendInfo friend = HandlerSettings.GetFriend("mhweb"); if (friend == null) { throw new Exception(String.Format("Failed to get friend info for: {0}", "mhweb")); } if (friend.UseAccess == FriendInfo.UseAccessType.oauth) { oslcClient = getOAuthClient(friend); } else { oslcClient = getBasicAuthClient(friend); } if (oslcClient == null) { throw new Exception(String.Format("Failed to get OSLC client for: {0}", "mhweb")); } return(oslcClient); }
// For test. Return the Resource as content that would be transmitted over wire. public ObjectContent getResourceAsMessage(EnterpriseChangeRequest ecr, String user) { return(getOslcClient().GetResourceAsMessage( ecr, OslcMediaType.APPLICATION_RDF_XML, OslcMediaType.APPLICATION_RDF_XML, HandlerSettings.getRESTHeaders(user))); }
// ============================================================= // Ignore all changes done by ourselves - i.e. the functional user used to handle save events. // Note that IF functional user would be logged in as regular user, events will not be handled. private bool ignoreSaveEvent(WorkItemChangedEvent ev) { bool ignore = HandlerSettings.IgnoreEventByUser( getStore().TeamProjectCollection, ev.ChangerTeamFoundationId); return(ignore); }
public static TfsTeamProjectCollection getTPC() { if (tpc == null) { tpc = new TfsTeamProjectCollection( new Uri(HandlerSettings.TFSUri), HandlerSettings.GetCredentials()); } return(tpc); }
// 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); } }
// TODO: This is cheating as we have hardcoded knowledge where release field is. // Need to make this fast, but to be correct we should use regular TFSMapper way. public bool IsProductInMaintenance(Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem wi) { String releaseName = wi.Fields[TFSMapper.TFS_FAULTY_PRODUCT].Value.ToString(); if (releaseName == null || releaseName.Length == 0) { // Should not happen - Mapping needs to be initialized HandlerSettings.LogMessage( "Can not tell if product is in maintenance as the Release name not defined.", HandlerSettings.LoggingLevel.WARN); return(false); } return(releaseToProductMap.ContainsKey(releaseName)); }
// ======================================================================= // Getters // Get long TR state name e.g. AS (Assigned) from short e.g. AS. public static String getLongTRState(String shortTRState) { if (mhStateToFullName.ContainsKey(shortTRState)) { return(mhStateToFullName[shortTRState]); } else { // Should not happen - unknown state to translate HandlerSettings.LogMessage( String.Format("State {0} is not recognized.", shortTRState), HandlerSettings.LoggingLevel.WARN); return(shortTRState); } }
// Get and initialize a basic auth client private OslcClient2 getBasicAuthClient(FriendInfo friend) { BasicAuthClient basicClient = null; String exMessage = ""; try { JazzRootServicesHelper2 helper = new JazzRootServicesHelper2( HandlerSettings.ClientUri, HandlerSettings.RootservicesUri); // Specific code - logging in basicClient = new BasicAuthClient(friend.EncodedCredentials); HttpClient client = basicClient.GetHttpClient(); // Get the Creation Factory for mhweb String user = friend.GetBasicAuthUser(); String serviceProviderUrl = basicClient.LookupServiceProviderUrl( helper.GetCatalogUrl(), "mhweb", HandlerSettings.getRESTHeaders(user)); HandlerSettings.CreationFactoryUri = basicClient.LookupCreationFactory( serviceProviderUrl, Constants.ENTERPRISE_CHANGE_MANAGEMENT_DOMAIN, Constants.TYPE_ENTERPRISE_CHANGE_REQUEST, HandlerSettings.getRESTHeaders(user)); } catch (Exception ex) { basicClient = null; exMessage = ex.Message; } if (basicClient == null || exMessage.Length > 0) { HandlerSettings.LogMessage( String.Format( "Failed to get a httpClient for basic auth." + "\nUsing client uri: {0}" + "\nUsing rootservices uri: {1}" + "\nMessage: {2}", HandlerSettings.ClientUri, HandlerSettings.RootservicesUri, exMessage), HandlerSettings.LoggingLevel.ERROR); } return(basicClient); }
// 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); }
public bool Load(String fileName) { releaseToProductMap = new Dictionary <String, PRIMProduct>(); try { XDocument doc = XDocument.Load(fileName); XElement mapping = doc.Element("mapping"); // Loop over all entries and add to the dictionary foreach (XElement spEntry in mapping.Elements("serviceProvider")) { // If the serviceProvider (TFS Project) is defined we only // care about events that are passed to that TFS Project serviceProvider = spEntry.Attribute("name").Value; foreach (XElement productEntry in spEntry.Elements("product")) { // TODO: Check if product info is case-sensitive String primProdNo = productEntry.Attribute("primProdNo").Value; String primRState = productEntry.Attribute("primRState").Value; PRIMProduct product = new PRIMProduct(primProdNo, primRState); String mapTo = productEntry.Attribute("mapTo").Value; // NOTE: We don't care about the team attribute for now. // String team = productEntry.Attribute("team").Value; //.ToLower();; releaseToProductMap.Add(mapTo, product); } } } catch (Exception ex) { // Error when reading mapping file - assume fatal HandlerSettings.LogMessage( "Error when reading the product mapping file: " + fileName + "\nError: " + ex.Message, HandlerSettings.LoggingLevel.ERROR); return(false); } return(true); }
// Get and initialize an oauth client private OslcClient2 getOAuthClient(FriendInfo friend) { JazzOAuthClient2 oauthClient = null; String exMessage = ""; try { JazzRootServicesHelper2 helper = new JazzRootServicesHelper2( HandlerSettings.ClientUri, HandlerSettings.RootservicesUri); oauthClient = helper.InitOAuthClient2(friend.ConsumerKey, friend.ConsumerSecret, friend.AccessToken, friend.AccessTokenSecret); // Get the Creation Factory for mhweb String serviceProviderUrl = oauthClient.LookupServiceProviderUrl(helper.GetCatalogUrl(), "mhweb"); HandlerSettings.CreationFactoryUri = oauthClient.LookupCreationFactory( serviceProviderUrl, Constants.ENTERPRISE_CHANGE_MANAGEMENT_DOMAIN, Constants.TYPE_ENTERPRISE_CHANGE_REQUEST); } catch (Exception ex) { oauthClient = null; exMessage = ex.Message; } if (oauthClient == null || exMessage.Length > 0) { HandlerSettings.LogMessage( String.Format( "Failed to get a httpClient for oauth." + "\nUsing client uri: {0}" + "\nUsing rootservices uri: {1}" + "\nMessage: {2}", HandlerSettings.ClientUri, HandlerSettings.RootservicesUri, exMessage), HandlerSettings.LoggingLevel.ERROR); } return(oauthClient); }
// =============================================== static public String GetTfsValueForEcrKey(String propertyName, WorkItem workItem) { List <String> values = TFSMapper.getInstance().mapToEcr(propertyName, workItem); if (values.Count == 0) { HandlerSettings.LogMessage( "Missing map entry for 'key': " + propertyName, HandlerSettings.LoggingLevel.INFO); return(""); } else if (values.Count > 1) { HandlerSettings.LogMessage( "More than one value for 'key': " + propertyName, HandlerSettings.LoggingLevel.WARN); } return(values[0]); }
// =============================================================== // Utility methods - could be moved elsewhere // For call to create TR we need Ericsson user id (also domain?) - seems like we have what we want here. // See http://stackoverflow.com/questions/19911368/using-the-tfs-2012-api-how-do-i-get-the-email-address-of-a-user static public TeamFoundationIdentity GetSignumForAssignedTo( Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem workItem) { TfsTeamProjectCollection tpc = workItem.Store.TeamProjectCollection; String userName = workItem.Fields[TFSMapper.TFS_OWNER].Value.ToString(); IIdentityManagementService mgmntService = tpc.GetService <IIdentityManagementService>(); TeamFoundationIdentity member = mgmntService.ReadIdentity( IdentitySearchFactor.DisplayName, userName, MembershipQuery.Direct, ReadIdentityOptions.ExtendedProperties); if (member == null) { HandlerSettings.LogMessage( String.Format("Failed to get user identity for user: {0}", userName), HandlerSettings.LoggingLevel.WARN); } return(member); }
// ========================================================= // 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); } }
// Check the Release is or just changed from a maintenance product. // TODO: This is cheating as we have hardcoded knowledge where release field is. // Need to make this fast, but to be correct we should use regular TFSMapper way. public EventType GetEventType( Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem wi, WorkItemChangedEvent notification, String user) { Field createTR = wi.Fields[TFSMapper.ERICSSON_DEFECT_CREATETR]; if (createTR == null || createTR.Value.ToString().Length == 0 || createTR.Value.ToString().Equals("No", StringComparison.OrdinalIgnoreCase)) { return(EventType.Ignore); } // Check project name. If not as defined for product mapping, disregard event. // This allows other projects on TFS server to be non-connected to TR system. if (serviceProvider.Length > 0 && !wi.Project.Name.Equals(serviceProvider, StringComparison.OrdinalIgnoreCase)) { return(EventType.Ignore); } bool inMaint = IsProductInMaintenance(wi); if (inMaint) { // Currenty selected release / product is in maintenance EventType evType = CheckDisconnect(wi, notification, user); if (evType == EventType.Disconnect) { return(EventType.Disconnect); } return(EventType.CreateUpdate); } // ======================================================================== // Check if we just changed from having a product in maintenance. Rare case, // then need to allow severing links and updating state of the workitem. if (notification.ChangeType == ChangeTypes.New) { // Can only happen for a changed WI, not a new. return(EventType.Ignore); } if (notification.ChangedFields == null || notification.ChangedFields.StringFields == null) { // No fields of interest are changed return(EventType.Ignore); } StringField[] changedFields = notification.ChangedFields.StringFields; for (int i = 0; i < changedFields.Length; i++) { String name = changedFields[i].ReferenceName; // 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 if (name.Equals(TFSMapper.TFS_FAULTY_PRODUCT)) { String releaseName = changedFields[i].OldValue; if (releaseName == null || releaseName.Length == 0) { // Should not happen - Mapping needs to be initialized HandlerSettings.LogMessage( "Can not tell if product is in maintenance as the Release name not defined.", HandlerSettings.LoggingLevel.WARN); return(EventType.Ignore); } if (releaseToProductMap.ContainsKey(releaseName)) { return(EventType.CreateUpdate); } else { return(EventType.Ignore); } } } return(EventType.Ignore); }
// Load attributes from the specified filename. If add is true we append mappings. public bool Load(String fileName, bool add) { // NOTE: Error if not called once first with add = false. if (!add) { forwardMap = new MultiMap <Property>(); inverseMap = new MultiMap <Property>(); // TODO: Adding a map to support case when looking up inverse based on TFS key // Should this be needed? Also for forward case? inverseMapTFSKey = new MultiMap <Property>(); notifyMap = new MultiMap <Property>(); } try { XDocument doc = XDocument.Load(fileName); XElement mapping = doc.Element("mapping"); // Loop over all entries and add to the dictionary foreach (XElement propEntry in mapping.Elements("property")) { // In TFS consumer case the serviceProvider (i.e. TFS Project) info is not relevant here // as the workitem (Bug) is in a project context. The serviceProvider info is used when // creating a Bug without the TFS context i.e. through the TFS OSLC Provider. Property property = processProperty(propEntry); foreach (XElement child in propEntry.Elements()) { switch (child.Name.LocalName) { case "default": processDefault(child, property); break; case "depends": processDepends(child, property); break; case "map": processMap(child, property); break; case "use": processUse(child, property); break; default: break; } } } } catch (Exception ex) { // Error when reading mapping file - assume fatal HandlerSettings.LogMessage( "Error when reading the attribute mapping file: " + fileName + "\nError: " + ex.Message + "\nStack: " + ex.StackTrace, HandlerSettings.LoggingLevel.ERROR); return(false); } return(true); }
// 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);; } }
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 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 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; } }
// ====================================================================================== // 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; }
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; }
// Return an EnterpriseChangeRequest with mapped values needed for the state transiton // If there is a fail with mapping some values, this will be captured in event log. // TODO: This could likely be described in a xml mapping file for configuration public static EnterpriseChangeRequest mapFromWorkitem( Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem workItem, Uri about, String action) { EnterpriseChangeRequest newEcr = new EnterpriseChangeRequest(); newEcr.SetAbout(about); // Create a mapped ECR based on suggested action switch (action) { case ACTION_CREATE: newEcr.SetTitle(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_TITLE, workItem)); newEcr.SetDescription(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_DESCRIPTION, workItem)); newEcr.SetCurrentMho(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_CURRENT_MHO, workItem)); newEcr.SetCustomer(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_CUSTOMER, workItem)); newEcr.SetProduct(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_PRODUCT, workItem)); newEcr.SetProductRevision(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_PRODUCT_REVISION, workItem)); newEcr.SetNodeProduct(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_NODE_PRODUCT, workItem)); newEcr.SetNodeProductRevision(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_NODE_PRODUCT_REVISION, workItem)); // Note: The field firstTechnicalContact also is needed for TR creation. This we retrieve from // the user in the create notification, and put in the REST call header as required by MHWeb. // Formally we have mapping specified in ERICSSON_DEFECT_CREATOR_SIGNUM, but not used. // Add the connected Bug as related link Uri relatedBug = new Uri(HandlerSettings.GetUriForBug(workItem.Id.ToString())); String label = workItem.Id.ToString() + ": " + workItem.Title; OSLC4Net.Core.Model.Link link = new OSLC4Net.Core.Model.Link(relatedBug, label); newEcr.AddRelatedChangeRequest(link); break; case ACTION_REGISTER_ROUTE: newEcr.SetAction(ACTION_REGISTER_ROUTE); newEcr.SetImpactOnISP(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_IMPACT_ON_ISP, workItem)); newEcr.SetPriority(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_PRIORITY, workItem)); newEcr.SetDiddet(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_DIDDET, workItem)); newEcr.SetActivity(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_ACTIVITY, workItem)); newEcr.SetFirstTechContactInfo(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_FIRST_TECHNICAL_CONTACT_INFO, workItem)); newEcr.SetCountry(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_COUNTRY, workItem)); newEcr.SetSite(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_SITE, workItem)); break; case ACTION_ASSIGN: newEcr.SetAction(ACTION_ASSIGN); newEcr.SetOwner(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_OWNER, workItem)); break; case ACTION_PROPOSE: newEcr.SetAction(ACTION_PROPOSE); newEcr.SetDiddet(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_DIDDET, workItem)); newEcr.SetActivity(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_ACTIVITY, workItem)); newEcr.SetFirstTechContactInfo(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_FIRST_TECHNICAL_CONTACT_INFO, workItem)); newEcr.SetExpectedImpactOnISP(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_EXPECTED_IMPACT_ON_ISP, workItem)); newEcr.SetAnswer(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_ANSWER, workItem)); newEcr.SetFaultCode(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_FAULTCODE, workItem)); newEcr.SetAnswerCode(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_ANSWER_CODE, workItem)); // Corrected Product info is mandatory for some answerCodes and optional for others. Here // we pass in all cases and let the bug.xml handle mandatoryness and mhweb complain if not // present. newEcr.SetCorrectedProduct( AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_CORRECTED_PRODUCT, workItem)); newEcr.SetCorrectedProductRevision( AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_CORRECTED_PRODUCT_REVISION, workItem)); newEcr.SetCorrectedNodeProduct( AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_CORRECTED_NODE_PRODUCT, workItem)); newEcr.SetCorrectedNodeProductRevision( AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_CORRECTED_NODE_PRODUCT_REVISION, workItem)); break; case ACTION_APPROVE: newEcr.SetAction(ACTION_APPROVE); break; case ACTION_VERIFY: newEcr.SetAction(ACTION_VERIFY); newEcr.SetCorrectedNodeProduct( AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_CORRECTED_NODE_PRODUCT, workItem)); newEcr.SetCorrectedNodeProductRevision( AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_CORRECTED_NODE_PRODUCT_REVISION, workItem)); break; case ACTION_ANSWER: newEcr.SetAction(ACTION_ANSWER); newEcr.SetCorrectedNodeProduct( AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_CORRECTED_NODE_PRODUCT, workItem)); newEcr.SetCorrectedNodeProductRevision( AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_CORRECTED_NODE_PRODUCT_REVISION, workItem)); break; case ACTION_ACCEPT: newEcr.SetAction(ACTION_ACCEPT); break; case ACTION_REJECT: newEcr.SetAction(ACTION_REJECT); newEcr.SetNotebook(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_NOTEBOOK, workItem)); break; case ACTION_DUPLICATE: newEcr.SetAction(ACTION_DUPLICATE); newEcr.SetPrimaryTR(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_PRIMARYTR, workItem)); break; case ACTION_UNDUPLICATE: newEcr.SetAction(ACTION_UNDUPLICATE); break; case ACTION_CHANGE_PRODUCT: newEcr.SetAction(ACTION_CHANGE_PRODUCT); newEcr.SetProduct(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_PRODUCT, workItem)); newEcr.SetProductRevision(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_PRODUCT_REVISION, workItem)); newEcr.SetNodeProduct(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_NODE_PRODUCT, workItem)); newEcr.SetNodeProductRevision(AttributesMapper.GetTfsValueForEcrKey(TFSMapper.ECM_NODE_PRODUCT_REVISION, workItem)); break; case ACTION_REACTIVATE: newEcr.SetAction(ACTION_REACTIVATE); break; case ACTION_FINISH: newEcr.SetAction(ACTION_FINISH); break; case ACTION_DISCONNECT: newEcr.SetAction(ACTION_DISCONNECT); break; } return(newEcr); }
// Return an EnterpriseChangeRequest with mapped values for the update public static EnterpriseChangeRequest mapFromUpdatedWorkitem( Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem workItem, Uri about, WorkItemChangedEvent notification) { EnterpriseChangeRequest newEcr = new EnterpriseChangeRequest(); newEcr.SetAbout(about); // The notification contain all changed fields. To understand what to // propagate to the client, we need to check which ecm fields that are // affected by the changes and are configured for notifyChange int noOfMappedChanged = 0; if (notification.ChangedFields != null) { // Most fields are String fields StringField[] changedStrFields = notification.ChangedFields.StringFields; if (changedStrFields != null) { for (int i = 0; i < changedStrFields.Length; i++) { String fieldName = changedStrFields[i].ReferenceName; noOfMappedChanged += mapFromUpdated(workItem, newEcr, fieldName); } } // For example Priority is an Integer field IntegerField[] changedIntFields = notification.ChangedFields.IntegerFields; if (changedIntFields != null) { for (int i = 0; i < changedIntFields.Length; i++) { String fieldName = changedIntFields[i].ReferenceName; noOfMappedChanged += mapFromUpdated(workItem, newEcr, fieldName); } } } // For example the Description is a Text field TextField[] changedTextFields = notification.TextFields; if (changedTextFields != null) { for (int i = 0; i < changedTextFields.Length; i++) { String fieldName = changedTextFields[i].ReferenceName; noOfMappedChanged += mapFromUpdated(workItem, newEcr, fieldName); } } // To find a change in the Comment/History one need to look at revision - 1. noOfMappedChanged += mapFromUpdated(workItem, newEcr, TFSMapper.TFS_HISTORY); // Need to send list of attachments in all cases when we have another update. So if we already have // an update (noOfMappedChanged > 0), send - otherwise, check if changed then send. if ((noOfMappedChanged > 0 || TFSMapper.getInstance().hasLinksChanged(workItem))) { noOfMappedChanged += mapFromUpdated(workItem, newEcr, TFSMapper.ERICSSON_DEFECT_HYPERLINK); } if (noOfMappedChanged > 0) { // More than 1 field that was mapped changed return(newEcr); } else { // No field of interest was changed HandlerSettings.LogMessage( String.Format("No mapped fields was updated for: {0}", workItem.Title), HandlerSettings.LoggingLevel.INFO); return(null); } }
// ======================================================================= // 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); } } }