/// <summary> /// Used to actually do the aggregating. /// </summary> /// <returns>true if a change was made. False if not</returns> public static bool Aggregate(IEnumerable<WorkItem> sourceWorkItems, WorkItem targetWorkItem, ConfigAggregatorItem configAggregatorItem) { if (configAggregatorItem.OperationType == OperationTypeEnum.Numeric) { return NumericAggregation(sourceWorkItems, targetWorkItem, configAggregatorItem); } else if (configAggregatorItem.OperationType == OperationTypeEnum.String) { return StringAggregation(sourceWorkItems, targetWorkItem, configAggregatorItem); } // This should never happen return false; }
/// <summary> /// Adds up all the values that need aggregating /// </summary> /// <returns>true if a change was made. False if not</returns> private static bool NumericAggregation(IEnumerable<WorkItem> sourceWorkItems, WorkItem targetWorkItem, ConfigAggregatorItem configAggregatorItem) { double aggregateValue = 0; // Iterate through all of the work items that we are pulling data from. // For link type of "Self" this will be just one item. For "Parent" this will be all of the co-children of the work item sent in the event. foreach (WorkItem sourceWorkItem in sourceWorkItems) { // Iterate through all of the TFS Fields that we are aggregating. foreach (ConfigItemType sourceField in configAggregatorItem.SourceItems) { double sourceValue = sourceWorkItem.GetField(sourceField.Name, 0.0); aggregateValue = configAggregatorItem.Operation.Perform(aggregateValue, sourceValue); } } if (aggregateValue != targetWorkItem.GetField<double>(configAggregatorItem.TargetItem.Name, 0)) { targetWorkItem[configAggregatorItem.TargetItem.Name] = aggregateValue; return true; } return false; }
// Load in the settings from file private static void GetSettings() { _configAggregatorItems = new List <ConfigAggregatorItem>(); XDocument doc; // Load the options if (string.IsNullOrEmpty(SettingsOverride)) { string xmlFileName = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase), "AggregatorItems.xml"); doc = XDocument.Load(xmlFileName); } else { doc = XDocument.Parse(SettingsOverride); } // Save off the TFS server name _tfsUri = doc.Element("AggregatorItems").Attribute("tfsServerUrl").Value; //Save off the Logging level _loggingVerbosity = doc.Element("AggregatorItems").LoadEnumAttribute("loggingVerbosity", MiscHelpers.LoggingLevel.None); // Get all the AggregatorItems loaded in foreach (XElement xmlAggItem in doc.Element("AggregatorItems").Elements()) { // Setup the actual item ConfigAggregatorItem aggItem = new ConfigAggregatorItem(); aggItem.Operation = xmlAggItem.LoadEnumAttribute("operation", OperationEnum.Sum); aggItem.LinkType = xmlAggItem.LoadEnumAttribute("linkType", ConfigLinkTypeEnum.Self); aggItem.OperationType = xmlAggItem.LoadEnumAttribute("operationType", OperationTypeEnum.Numeric); aggItem.LinkLevel = xmlAggItem.LoadAttribute("linkLevel", 1); aggItem.Name = xmlAggItem.LoadAttribute("name", "Name not set"); aggItem.WorkItemType = xmlAggItem.Attribute("workItemType").Value; aggItem.TargetWorkItemType = xmlAggItem.LoadAttribute("targetWorkItemType", ""); // Iterate through all the sub items (Mappings, source and target items.)) foreach (XElement xmlConfigItem in xmlAggItem.Elements()) { // If this is a target item then add it as such if (xmlConfigItem.Name == "TargetField") { aggItem.TargetField = new ConfigItemType { Name = xmlConfigItem.Attribute("name").Value }; } // If this is a source item then add it as such if (xmlConfigItem.Name == "SourceField") { aggItem.SourceFields.Add(new ConfigItemType { Name = xmlConfigItem.Attribute("name").Value }); } // If this is an outputFormat item then add it as such if (xmlConfigItem.Name == "OutputFormat") { aggItem.OutputFormat = xmlConfigItem.Attribute("formatString").Value; } // If this is conditions (for the target) then read them in. if (xmlConfigItem.Name == "Conditions") { // Iterate all the conditions we have. foreach (XElement xmlCondition in xmlConfigItem.Elements()) { Condition condition = new Condition { LeftFieldName = xmlCondition.LoadAttribute("leftField", ""), LeftValue = xmlCondition.LoadAttribute <string>("leftValue", null), CompOperator = xmlCondition.LoadEnumAttribute("operator", ComparisionOperator.EqualTo), RightValue = xmlCondition.LoadAttribute <string>("rightValue", null), RightFieldName = xmlCondition.LoadAttribute <string>("rightField", null) }; aggItem.Conditions.Add(condition); } } // If this is the mappings then read them in if (xmlConfigItem.Name == "Mappings") { // Iterate all the mappings we have. foreach (XElement xmlMappingItem in xmlConfigItem.Elements()) { Mapping mapping = new Mapping(); // Read in the target and source values (we set the target field // to this value if all of the source fields are in the list of source values) mapping.TargetValue = xmlMappingItem.Attribute("targetValue").Value; // load in the inclusivity of the mapping (default to "And") mapping.Inclusive = xmlMappingItem.LoadAttribute("inclusive", "And") == "And"; foreach (XElement xmlSourceValue in xmlMappingItem.Elements()) { mapping.SourceValues.Add(xmlSourceValue.Value); } aggItem.Mappings.Add(mapping); } } } // add this one to the list _configAggregatorItems.Add(aggItem); } // Indicate that we don't need to load the settings again. _settingsRetrieved = true; }
// Load in the settings from file private static void GetSettings() { _configAggregatorItems = new List<ConfigAggregatorItem>(); // Load the options string xmlFileName = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase), "AggregatorItems.xml"); XDocument doc = XDocument.Load(xmlFileName); // Save off the TFS server name _tfsUri = doc.Element("AggregatorItems").Attribute("tfsServerUrl").Value; //Save off the Logging level _loggingVerbosity = doc.Element("AggregatorItems").LoadEnumAttribute("loggingVerbosity", MiscHelpers.LoggingLevel.None); // Get all the AggregatorItems loaded in foreach (XElement xmlAggItem in doc.Element("AggregatorItems").Elements()) { // Setup the actual item ConfigAggregatorItem aggItem = new ConfigAggregatorItem(); aggItem.Operation = xmlAggItem.LoadEnumAttribute("operation", OperationEnum.Sum); aggItem.LinkType = xmlAggItem.LoadEnumAttribute("linkType", ConfigLinkTypeEnum.Self); aggItem.OperationType = xmlAggItem.LoadEnumAttribute("operationType", OperationTypeEnum.Numeric); aggItem.LinkLevel = xmlAggItem.LoadAttribute("linkLevel", 1); aggItem.Name = xmlAggItem.LoadAttribute("name", "Name not set"); aggItem.WorkItemType = xmlAggItem.Attribute("workItemType").Value; // Iterate through all the sub items (Mappings, source and target items.)) foreach (XElement xmlConfigItem in xmlAggItem.Elements()) { // If this is a target item then add it as such if (xmlConfigItem.Name == "TargetItem") { aggItem.TargetItem = new ConfigItemType { Name = xmlConfigItem.Attribute("name").Value }; } // If this is a source item then add it as such if (xmlConfigItem.Name == "SourceItem") { aggItem.SourceItems.Add(new ConfigItemType { Name = xmlConfigItem.Attribute("name").Value }); } // If this is conditions (for the target) then read them in. if (xmlConfigItem.Name == "Conditions") { // Iterate all the conditions we have. foreach (XElement xmlCondition in xmlConfigItem.Elements()) { Condition condition = new Condition(); condition.LeftFieldName = xmlCondition.LoadAttribute("leftField", ""); condition.CompOperator = xmlCondition.LoadEnumAttribute("operator", ComparisionOperator.EqualTo); condition.RightValue = xmlCondition.LoadAttribute<string>("rightValue", null); condition.RightFieldName = xmlCondition.LoadAttribute<string>("rightField", null); aggItem.Conditions.Add(condition); } } // If this is the mappings then read them in if (xmlConfigItem.Name == "Mappings") { // Iterate all the mappings we have. foreach (XElement xmlMappingItem in xmlConfigItem.Elements()) { Mapping mapping = new Mapping(); // Read in the target and source values (we set the target field // to this value if all of the source fields are in the list of source values) mapping.TargetValue = xmlMappingItem.Attribute("targetValue").Value; // load in the inclusivity of the mapping (default to "And") mapping.Inclusive = xmlMappingItem.LoadAttribute("inclusive", "And") == "And"; foreach (XElement xmlSourceValue in xmlMappingItem.Elements()) { mapping.SourceValues.Add(xmlSourceValue.Value); } aggItem.Mappings.Add(mapping); } } } // add this one to the list _configAggregatorItems.Add(aggItem); } // Indicate that we don't need to load the settings again. _settingsRetrieved = true; }
/// <summary> /// Checks to see if all of the source fields values are in one of the mappings. /// The first mapping that is found that is a match is used. It will apply the target value from the mapping to /// the Target Field. /// </summary> /// <returns>true if a change was made. False if not</returns> private static bool StringAggregation(IEnumerable <WorkItem> sourceWorkItems, WorkItem targetWorkItem, ConfigAggregatorItem configAggregatorItem) { string aggregateValue = ""; bool aggregateFound = false; // Iterate through the mappings until (or if) we find one that we match on foreach (Mapping mapping in configAggregatorItem.Mappings) { bool mappingMatches; // The default value depends on the inclusivity of the mappings. // If it is And then we are trying to prove that all of them match so we start with true, any mismatches will cause us to set to false. // If it is Or then we are trying to prove that only one them match to succeed so we start with false, any matches will cause us to set to true. if (mapping.Inclusive) { mappingMatches = true; } else { mappingMatches = false; } // Iterate through all of the work items that we are pulling data from. // For link type of "Self" this will be just one item. For "Parent" this will // be all of the co-children of the work item sent in the event. foreach (WorkItem sourceWorkItem in sourceWorkItems) { // Iterate through all of the TFS Fields that we are aggregating. foreach (ConfigItemType sourceField in configAggregatorItem.SourceItems) { // Get the value of the sourceField on the sourceWorkItem string sourceValue = sourceWorkItem.GetField(sourceField.Name, ""); // Check to see if the value we have is not in the list of SourceValues // If it is not then this mapping is not going to be satisfied because this source item // breaks it (because we are inclusively checking (i.e. "And")). if (!mapping.SourceValues.Contains(sourceValue) && (mapping.Inclusive)) { // it was not in the list. We are done with this mapping. // if we get here then this is an "And" mapping that failed. mappingMatches = false; break; } // Check to see if the value we have is in the list of SourceValues // If it is, this mapping is satisfied because we are non inclusive (i.e. "Or") if (mapping.SourceValues.Contains(sourceValue) && (!mapping.Inclusive)) { // it was in the list. We are done with this mapping. // If we get here then this was an "Or" mapping that succeeded mappingMatches = true; break; } } // If this is an "And" and mapping does not match then we may as well be done with this iteration of work items. if ((!mappingMatches) && (mapping.Inclusive)) { break; } // If this is an "Or" and mapping does match then we need to be done. if ((mappingMatches) && (!mapping.Inclusive)) { break; } } // If the mapping matched then we are done looking if (mappingMatches) { aggregateValue = mapping.TargetValue; aggregateFound = true; break; } } if (aggregateFound) { // see if we need to make a change: if (targetWorkItem[configAggregatorItem.TargetItem.Name].ToString() != aggregateValue) { // If this is the "State" field then we may have do so special stuff // (to get the state they want from where we are). If not then just set the value. if (configAggregatorItem.TargetItem.Name != "State") { targetWorkItem[configAggregatorItem.TargetItem.Name] = aggregateValue; } else { targetWorkItem.TransitionToState(aggregateValue, "TFS Aggregator: "); } return(true); } } return(false); }
/// <summary> /// Adds up all the values that need aggregating /// </summary> /// <returns>true if a change was made. False if not</returns> private static bool NumericAggregation(IEnumerable <WorkItem> sourceWorkItems, WorkItem targetWorkItem, ConfigAggregatorItem configAggregatorItem) { double aggregateValue = 0; // Iterate through all of the work items that we are pulling data from. // For link type of "Self" this will be just one item. For "Parent" this will be all of the co-children of the work item sent in the event. foreach (WorkItem sourceWorkItem in sourceWorkItems) { // Iterate through all of the TFS Fields that we are aggregating. foreach (ConfigItemType sourceField in configAggregatorItem.SourceItems) { double sourceValue = sourceWorkItem.GetField(sourceField.Name, 0.0); aggregateValue = configAggregatorItem.Operation.Perform(aggregateValue, sourceValue); } } if (aggregateValue != targetWorkItem.GetField <double>(configAggregatorItem.TargetItem.Name, 0)) { targetWorkItem[configAggregatorItem.TargetItem.Name] = aggregateValue; return(true); } return(false); }
/// <summary> /// Used to actually do the aggregating. /// </summary> /// <returns>true if a change was made. False if not</returns> public static bool Aggregate(IEnumerable <WorkItem> sourceWorkItems, WorkItem targetWorkItem, ConfigAggregatorItem configAggregatorItem) { if (configAggregatorItem.OperationType == OperationTypeEnum.Numeric) { return(NumericAggregation(sourceWorkItems, targetWorkItem, configAggregatorItem)); } else if (configAggregatorItem.OperationType == OperationTypeEnum.String) { return(StringAggregation(sourceWorkItems, targetWorkItem, configAggregatorItem)); } // This should never happen return(false); }
/// <summary> /// Checks to see if all of the source fields values are in one of the mappings. /// The first mapping that is found that is a match is used. It will apply the target value from the mapping to /// the Target Field. /// </summary> /// <returns>true if a change was made. False if not</returns> private static bool StringAggregation(IEnumerable<WorkItem> sourceWorkItems, WorkItem targetWorkItem, ConfigAggregatorItem configAggregatorItem) { string aggregateValue = ""; bool aggregateFound = false; // Iterate through the mappings until (or if) we find one that we match on foreach (Mapping mapping in configAggregatorItem.Mappings) { bool mappingMatches; // The default value depends on the inclusivity of the mappings. // If it is And then we are trying to prove that all of them match so we start with true, any mismatches will cause us to set to false. // If it is Or then we are trying to prove that only one them match to succeed so we start with false, any matches will cause us to set to true. if (mapping.Inclusive) mappingMatches = true; else mappingMatches = false; // Iterate through all of the work items that we are pulling data from. // For link type of "Self" this will be just one item. For "Parent" this will // be all of the co-children of the work item sent in the event. foreach (WorkItem sourceWorkItem in sourceWorkItems) { // Iterate through all of the TFS Fields that we are aggregating. foreach (ConfigItemType sourceField in configAggregatorItem.SourceItems) { // Get the value of the sourceField on the sourceWorkItem string sourceValue = sourceWorkItem.GetField(sourceField.Name, ""); // Check to see if the value we have is not in the list of SourceValues // If it is not then this mapping is not going to be satisfied because this source item // breaks it (because we are inclusively checking (i.e. "And")). if (!mapping.SourceValues.Contains(sourceValue) &&(mapping.Inclusive)) { // it was not in the list. We are done with this mapping. // if we get here then this is an "And" mapping that failed. mappingMatches = false; break; } // Check to see if the value we have is in the list of SourceValues // If it is, this mapping is satisfied because we are non inclusive (i.e. "Or") if (mapping.SourceValues.Contains(sourceValue) && (!mapping.Inclusive)) { // it was in the list. We are done with this mapping. // If we get here then this was an "Or" mapping that succeeded mappingMatches = true; break; } } // If this is an "And" and mapping does not match then we may as well be done with this iteration of work items. if ((!mappingMatches) && (mapping.Inclusive)) break; // If this is an "Or" and mapping does match then we need to be done. if ((mappingMatches) && (!mapping.Inclusive)) break; } // If the mapping matched then we are done looking if (mappingMatches) { aggregateValue = mapping.TargetValue; aggregateFound = true; break; } } if (aggregateFound) { // see if we need to make a change: if (targetWorkItem[configAggregatorItem.TargetItem.Name].ToString() != aggregateValue) { // If this is the "State" field then we may have do so special stuff // (to get the state they want from where we are). If not then just set the value. if (configAggregatorItem.TargetItem.Name != "State") targetWorkItem[configAggregatorItem.TargetItem.Name] = aggregateValue; else targetWorkItem.TransitionToState(aggregateValue, "TFS Aggregator: "); return true; } } return false; }
/// <summary> /// This is the one where all the magic starts. Main() so to speak. I will load the settings, connect to tfs and apply the aggregation rules. /// </summary> 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; int currentAggregationId = 0; int workItemId = 0; string currentAggregationName = string.Empty; try { if (notificationType == NotificationType.Notification && notificationEventArgs is WorkItemChangedEvent) { // Change this object to be a type we can easily get into WorkItemChangedEvent ev = notificationEventArgs as WorkItemChangedEvent; // Connect to the setting file and load the location of the TFS server string tfsUri = TFSAggregatorSettings.TFSUri; // Connect to TFS so we are ready to get and send data. Store store = new Store(tfsUri); // Get the id of the work item that was just changed by the user. workItemId = ev.CoreFields.IntegerFields[0].NewValue; // Download the work item so we can update it (if needed) WorkItem eventWorkItem = store.Access.GetWorkItem(workItemId); string workItemTypeName = eventWorkItem.Type.Name; List <WorkItem> workItemsToSave = new List <WorkItem>(); if (TFSAggregatorSettings.LoggingIsEnabled) { MiscHelpers.LogMessage(String.Format("Change detected to {0} [{1}]", workItemTypeName, workItemId)); MiscHelpers.LogMessage(String.Format("{0}Processing {1} AggregationItems", " ", TFSAggregatorSettings.ConfigAggregatorItems.Count.ToString())); } // Apply the aggregation rules to the work item foreach (ConfigAggregatorItem configAggregatorItem in TFSAggregatorSettings.ConfigAggregatorItems) { IEnumerable <WorkItem> sourceWorkItems = null; WorkItem targetWorkItem = null; currentAggregationName = configAggregatorItem.Name; // Check to make sure that the rule applies to the work item type we have if (eventWorkItem.Type.Name == configAggregatorItem.WorkItemType) { if (TFSAggregatorSettings.LoggingIsEnabled) { MiscHelpers.LogMessage(String.Format("{0}[Entry {2}] Aggregation '{3}' applies to {1} work items", " ", workItemTypeName, currentAggregationId, currentAggregationName)); } // Use the link type to see what the work item we are updating is if (configAggregatorItem.LinkType == ConfigLinkTypeEnum.Self) { // We are updating the same workitem that was sent in the event. sourceWorkItems = new List <WorkItem> { eventWorkItem }; targetWorkItem = eventWorkItem; if (TFSAggregatorSettings.LoggingIsEnabled) { MiscHelpers.LogMessage(String.Format("{0}{0}{0}Aggregation applies to SELF. ({1} {2})", " ", workItemTypeName, workItemId)); } // Make sure that all conditions are true before we do the aggregation // If any fail then we don't do this aggregation. if (!configAggregatorItem.Conditions.AreAllConditionsMet(targetWorkItem)) { if (TFSAggregatorSettings.LoggingIsEnabled) { MiscHelpers.LogMessage(String.Format("{0}{0}All conditions for aggregation are not met.", " ")); } currentAggregationId++; continue; } if (TFSAggregatorSettings.LoggingIsEnabled) { MiscHelpers.LogMessage(String.Format("{0}{0}All conditions for aggregation are met.", " ")); } } // We are aggregating to the parent else if (configAggregatorItem.LinkType == ConfigLinkTypeEnum.Parent) { bool parentLevelFound = true; // Go up the tree till we find the level of parent that we are looking for. WorkItem iterateToParent = eventWorkItem; for (int i = 0; i < configAggregatorItem.LinkLevel; i++) { // Load the parent from the saved list (if we have it in there) or just load it from the store. WorkItem nullCheck = iterateToParent.GetParentFromListOrStore(workItemsToSave, store); // if (nullCheck != null) { iterateToParent = nullCheck; } else { parentLevelFound = false; } } // If we did not find the correct parent then we are done with this aggregation. if (!parentLevelFound) { if (TFSAggregatorSettings.LoggingIsEnabled) { MiscHelpers.LogMessage(String.Format("{0}{0}{0}Couldn't find a PARENT {2} {4} up from {3} [{1}]. This aggregation will not continue.", " ", workItemId, configAggregatorItem.LinkLevel, workItemTypeName, configAggregatorItem.LinkLevel > 1 ? "levels" : "level")); } currentAggregationId++; continue; } if (TFSAggregatorSettings.LoggingIsEnabled) { MiscHelpers.LogMessage(String.Format("{0}{0}{0}Found {1} [{2}] {3} {4} up from {5} [{6}]. Aggregation continues.", " ", iterateToParent.Type.Name, iterateToParent.Id, configAggregatorItem.LinkLevel, configAggregatorItem.LinkLevel > 1 ? "levels" : "level", workItemTypeName, workItemId)); } targetWorkItem = iterateToParent; // Make sure that all conditions are true before we do the aggregation // If any fail then we don't do this aggregation. if (!configAggregatorItem.Conditions.AreAllConditionsMet(targetWorkItem)) { if (TFSAggregatorSettings.LoggingIsEnabled) { MiscHelpers.LogMessage(String.Format("{0}{0}All conditions for parent aggregation are not met", " ")); } currentAggregationId++; continue; } if (TFSAggregatorSettings.LoggingIsEnabled) { MiscHelpers.LogMessage(String.Format("{0}{0}All conditions for parent aggregation are met", " ")); } // Get the children down how ever many link levels were specified. List <WorkItem> iterateFromParents = new List <WorkItem> { targetWorkItem }; for (int i = 0; i < configAggregatorItem.LinkLevel; i++) { List <WorkItem> thisLevelOfKids = new List <WorkItem>(); // Iterate all the parents to find the children of current set of parents foreach (WorkItem iterateFromParent in iterateFromParents) { thisLevelOfKids.AddRange(iterateFromParent.GetChildrenFromListOrStore(workItemsToSave, store)); } iterateFromParents = thisLevelOfKids; } // remove the kids that are not the right type that we are working with ConfigAggregatorItem configAggregatorItemClosure = configAggregatorItem; iterateFromParents.RemoveAll(x => x.Type.Name != configAggregatorItemClosure.WorkItemType); sourceWorkItems = iterateFromParents; } // Do the actual aggregation now bool changeMade = Aggregator.Aggregate(sourceWorkItems, targetWorkItem, configAggregatorItem); // If we made a change then add this work item to the list of items to save. if (changeMade) { // Add the target work item to the list of work items to save. workItemsToSave.AddIfUnique(targetWorkItem); } } else { if (TFSAggregatorSettings.LoggingIsEnabled) { MiscHelpers.LogMessage(String.Format("{0}[Entry {2}] Aggregation '{3}' does not apply to {1} work items", " ", workItemTypeName, currentAggregationId, currentAggregationName)); } } currentAggregationId++; } // Save any changes to the target work items. workItemsToSave.ForEach(x => { bool isValid = x.IsValid(); if (TFSAggregatorSettings.LoggingIsEnabled) { MiscHelpers.LogMessage(String.Format("{0}{0}{0}{1} [{2}] {3} valid to save. {4}", " ", x.Type.Name, x.Id, isValid ? "IS" : "IS NOT", String.Format("\n{0}{0}{0}{0}Invalid fields: {1}", " ", MiscHelpers.GetInvalidWorkItemFieldsList(x).ToString()))); } if (isValid) { x.PartialOpen(); x.Save(); } }); MiscHelpers.AddRunSeparatorToLog(); } } catch (Exception e) { string message = String.Format("Exception encountered processing Work Item [{2}]: {0} \nStack Trace:{1}", e.Message, e.StackTrace, workItemId); if (e.InnerException != null) { message += String.Format("\n Inner Exception: {0} \nStack Trace:{1}", e.InnerException.Message, e.InnerException.StackTrace); } MiscHelpers.LogMessage(message, true); } return(EventNotificationStatus.ActionPermitted); }
/// <summary> /// Copies a value from one workitem into another. /// Values are copied from the target into the source (the event item) /// </summary> /// <returns>true if a change was made. False if not</returns> private static IWorkItem CopyToAggregation(IWorkItem childWorkItem, IWorkItem parentWorkItem, ConfigAggregatorItem configAggregatorItem) { var aggregateSourceValues = new List <string>(); // Iterate through all of the TFS Fields that we are aggregating. foreach (ConfigItemType sourceField in configAggregatorItem.SourceFields) { object value = parentWorkItem.GetField(sourceField.Name, (object)null); // Get the value of the sourceField on the sourceWorkItem and add it to the list string sourceValue = (value ?? "").ToString(); aggregateSourceValues.Add(sourceValue); } var resultValue = string.Format(configAggregatorItem.OutputFormat, aggregateSourceValues.ToArray()); // see if we need to make a change: if (childWorkItem[configAggregatorItem.TargetField.Name].ToString() != resultValue) { //We don't want to use copyfrom for the state. There are other ways of doing that. if (configAggregatorItem.TargetField.Name != "State") { childWorkItem[configAggregatorItem.TargetField.Name] = resultValue; return(childWorkItem); } } return(null); }
/// <summary> /// Used to acutally do the aggregating. /// </summary> /// <returns>true if a change was made. False if not</returns> public static IWorkItem Aggregate(IWorkItem sourceWorkItem, IEnumerable <IWorkItem> sourceWorkItems, IWorkItem targetWorkItem, ConfigAggregatorItem configAggregatorItem) { if (configAggregatorItem.OperationType == OperationTypeEnum.Numeric) { return(NumericAggregation(sourceWorkItems, targetWorkItem, configAggregatorItem)); } if (configAggregatorItem.OperationType == OperationTypeEnum.String) { return(StringAggregation(sourceWorkItems, targetWorkItem, configAggregatorItem)); } if (configAggregatorItem.OperationType == OperationTypeEnum.CopyFrom) { return(CopyFromAggregation(sourceWorkItem, targetWorkItem, configAggregatorItem)); } if (configAggregatorItem.OperationType == OperationTypeEnum.CopyTo) { return(CopyToAggregation(targetWorkItem, sourceWorkItem, configAggregatorItem)); } // This should never happen return(null); }
/// <summary> /// Copies a value from one workitem into another. /// Values are copied from the target into the source (the event item) /// </summary> /// <returns>true if a change was made. False if not</returns> private static IWorkItem CopyFromAggregation(IWorkItem childWorkItem, IWorkItem parentWorkItem, ConfigAggregatorItem configAggregatorItem) { //Source is the item just updated. It's the one we want to copy values to, not from. //It means that this code is a little confusing since source and target have reversed meanings. //For that reason we switch things around var aggregateSourceValues = new List <string>(); // Iterate through all of the TFS Fields that we are aggregating. foreach (ConfigItemType sourceField in configAggregatorItem.SourceFields) { object value = parentWorkItem.GetField(sourceField.Name, (object)null); // Get the value of the sourceField on the sourceWorkItem and add it to the list string sourceValue = (value ?? "").ToString(); aggregateSourceValues.Add(sourceValue); } var resultValue = string.Format(configAggregatorItem.OutputFormat, aggregateSourceValues.ToArray()); // see if we need to make a change: if (childWorkItem[configAggregatorItem.TargetField.Name].ToString() != resultValue) { //We don't want to use copyfrom for the state. There are other ways of doing that. if (configAggregatorItem.TargetField.Name != "State") { childWorkItem[configAggregatorItem.TargetField.Name] = resultValue; return(childWorkItem); } } return(null); }