/// <summary> /// Uses properties of the SimpleWorkItem to determine this PBI's color. /// </summary> internal static string GetPbiColor(SimpleWorkItem pbi) { // Mark PBIs with no effort as red. if (pbi.Effort == 0) { return "#BB0000"; } // Otherwise, color by the PBI's state. switch (pbi.State) { case "New": return "#0000BB"; case "Approved": return "#990099"; case "Committed": return "#FF6600"; case "Done": return "#00BB00"; case "Removed": return "#BB0000"; default: // Unknown state. return "#FFFFFF"; } }
/// <summary> /// Checks each filter level to see if this item should be ignored. /// </summary> internal bool IsNotIgnored(SimpleWorkItem simpleWorkItem) { // If there's no filter provided, it's not ignored. var filterCollection = _globalManager.FilterLevels; if (filterCollection == null) { return true; } // Check if any filter level's filter wants this item ignored. foreach (var filterLevel in SimpleWorkItem.NonPbiLevels) { // Get the filter level keys. HashSet<string> filterKeys; if (filterCollection.TryGetValue(filterLevel, out filterKeys)) { // Check if the item's associated key is not contained in the filter level keys. var keyToCheck = simpleWorkItem.GetKey(filterLevel); if (!filterKeys.Contains(keyToCheck)) { return false; } } } // It was not found in any of the filters. return true; }
/// <summary> /// Adds the work item to the main nested collection, creating branches as necessary. /// </summary> internal void AddToTree(SimpleWorkItem simpleWorkItem) { // Start from the beginning of the collection and work down the tree levels. var currentTree = _globalManager.Tree; foreach (var treeLevel in _globalManager.TreeLevels) { // Should never happen. if (currentTree == null) { throw new Exception("Tree was null."); } // Get the relevant data from this item for this tree level to use as a key. var itemKey = simpleWorkItem.GetKey(treeLevel); // Every item has a set of properties associated with it. var propertiesManager = new PropertiesManager(_globalManager); if (treeLevel == "PBI") // Final level reached, add the work item. This level is optional. { propertiesManager.SetPbiProperties(simpleWorkItem); currentTree.Add(itemKey, propertiesManager.Properties); } else // Get the appropriate collection, making a new one if necessary. { if (!currentTree.ContainsKey(itemKey)) // This key's collection doesn't exist. { // Since it's new, add it to a grouping list for synchronizing size/color across a tree level. var group = _globalManager.Groupings.GetOrAdd(treeLevel, new ConcurrentDictionary<string, string>()); group.TryAdd(itemKey, ""); // The last level won't have children so don't make a new list for children. // This distinction is helpful in other places for determining if there are any children by checking if properties["children"] = null. if (treeLevel != _globalManager.TreeLevels.Last()) { propertiesManager.AddChildren(); } // Some tree levels have specific properties. propertiesManager.SetLevelProperties(treeLevel, itemKey); // Now that properties are determined, add it to the collection. currentTree.Add(itemKey, propertiesManager.Properties); } else // This key's collection already exists. { // Get the existing properties instead. propertiesManager.Properties = currentTree[itemKey]; } // Go a level deeper for the next iteration. currentTree = propertiesManager.GetChildren(); } // Add to the total effort for this grouping/item at this tree level. propertiesManager.AddEffort(simpleWorkItem.Effort); } }
/// <summary> /// Pulls information from work item types. /// </summary> internal void StoreWorkItem(string type, SimpleWorkItem simpleWorkItem) { switch (type) { case "PBIs": StorePbi(simpleWorkItem); break; case "Themes": StoreTheme(simpleWorkItem); break; case "Filters": StoreFilter(simpleWorkItem); break; case "Teams": StoreTeam(simpleWorkItem); break; default: throw new Exception("Work item type: " + type + ", is not supported."); } }
/// <summary> /// Stores a pre-defined list of properties from work items into a simple object. /// </summary> internal static SimpleWorkItem BuildSimpleWorkItem(WorkItem workItem, DisplayFieldList displayFieldList) { var simpleWorkItem = new SimpleWorkItem(); foreach (FieldDefinition fieldDefinition in displayFieldList) { // Each case needs the field's value. var fieldValue = GetFieldValue(workItem, fieldDefinition.Name); // Modify properties here before storing them if they aren't in a desirable format, such as replacing team codes with team names. switch (fieldDefinition.Name) { case "Effort": double effort; double.TryParse(fieldValue, out effort); simpleWorkItem.Effort = effort; break; case "ID": int id; int.TryParse(fieldValue, out id); simpleWorkItem.Id = id; break; case "Backlog Priority": double priority; double.TryParse(fieldValue, out priority); simpleWorkItem.Priority = priority; break; case "Iteration Path": // Remove the project name, or use a constant if it's solely the project name. var quarter = fieldValue.Contains("\\") ? fieldValue.Substring(fieldValue.IndexOf('\\') + 1) : SimpleWorkItem.GetMissingString("Quarter"); var sprint = fieldValue.Contains("\\") ? fieldValue.Substring(fieldValue.IndexOf('\\') + 1) : SimpleWorkItem.GetMissingString("Sprint"); // Remove the sprint part for determining quarter, if needed. quarter = quarter.Contains("Sprint") ? quarter.Substring(0, quarter.LastIndexOf('\\')) : quarter; simpleWorkItem.Quarter = quarter; // Normally would remove the quarter/year part for determining sprint here, but I need that for sorting by date. simpleWorkItem.Sprint = sprint; break; case "State": simpleWorkItem.State = fieldValue; break; case "Node Name": simpleWorkItem.Team = fieldValue; switch (simpleWorkItem.Team) { // Some hardcoded team names in TFS. case "Advantage": case "Identity": simpleWorkItem.Team = SimpleWorkItem.GetMissingString("Team"); break; case "STL": simpleWorkItem.Team = "Vulcan"; break; case "T1": simpleWorkItem.Team = "Loki"; break; case "T2": simpleWorkItem.Team = "Anubis"; break; case "T3": simpleWorkItem.Team = "Janus"; break; case "TC": simpleWorkItem.Team = "Atlas"; break; case "TS": simpleWorkItem.Team = "Athena"; break; } break; case "Product": simpleWorkItem.Product = fieldValue == string.Empty ? SimpleWorkItem.GetMissingString("Product") : fieldValue; break; case "Program Theme": simpleWorkItem.Theme = fieldValue == string.Empty ? SimpleWorkItem.GetMissingString("Theme") : fieldValue; break; case "Title": simpleWorkItem.Title = fieldValue; break; case "Work Item Type": simpleWorkItem.Type = fieldValue; break; case "Description HTML": // Not PBI-related, used for main theme coloring. var color = ThemeManager.ParseHtmlForColor(fieldValue); simpleWorkItem.MainThemeColor = color; break; case "Mean Velocity": // Not PBI-related, used for teams. double velocity; double.TryParse(fieldValue, out velocity); simpleWorkItem.MeanVelocity = velocity; break; } } // Themes are currently the only optional field for the PBIs query so if it's not an included column, assign it the missing theme value. if (simpleWorkItem.Theme == null) { simpleWorkItem.Theme = SimpleWorkItem.GetMissingString("Theme"); } return simpleWorkItem; }
/// <summary> /// Stores filter names into a collection. /// </summary> void StoreFilter(SimpleWorkItem simpleWorkItem) { foreach (var dataLevel in SimpleWorkItem.NonPbiLevels) { var dataKey = simpleWorkItem.GetKey(dataLevel); var group = _globalManager.Groupings.GetOrAdd(dataLevel, new ConcurrentDictionary<string, string>()); group.TryAdd(dataKey, ""); } }
/// <summary> /// Stores information specific to themes. /// </summary> void StoreTheme(SimpleWorkItem simpleWorkItem) { // Some themes have shortcuts that should be used instead. var trueTheme = ThemeManager.GetTrueTheme(simpleWorkItem.Title); // Make this possibly already parsed title match the parsing conventions. var mainThemeName = GlobalManager.GetMainTheme(trueTheme); // Store its priority. try { _globalManager.ThemePriorities.Add(mainThemeName, simpleWorkItem.Priority); } catch (ArgumentException) { // TODO: Ignoring same name themes. } // Store its color. if (!_globalManager.MainThemeColors.TryAdd(mainThemeName, simpleWorkItem.MainThemeColor)) { // TODO: Ignoring same name themes. } }
/// <summary> /// Stores team velocities into a collection. /// </summary> void StoreTeam(SimpleWorkItem simpleWorkItem) { _globalManager.TeamVelocities.TryAdd(simpleWorkItem.Title, simpleWorkItem.MeanVelocity); }
/// <summary> /// Stores a SimpleWorkItem in a logically nested collection for efficient iteration. /// </summary> void StorePbi(SimpleWorkItem simpleWorkItem) { // Only PBIs are currently supported. if (simpleWorkItem.Type != "Product Backlog Item") { return; } // Don't store this item if it's marked to be ignored by any of the filter levels. if (IsNotIgnored(simpleWorkItem)) { _treeManager.AddToTree(simpleWorkItem); } }
/// <summary> /// Sets properties associated specifically with PBIs. /// </summary> internal void SetPbiProperties(SimpleWorkItem simpleWorkItem) { Properties["priority"] = simpleWorkItem.Priority; // Useful in SortManager. Properties["simpleWorkItem"] = simpleWorkItem; }