forked from Vaccano/TFS-Aggregator
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathWorkItemHelper.cs
284 lines (247 loc) · 12 KB
/
WorkItemHelper.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using System.Text;
namespace TFSAggregator
{
public static class WorkItemHelper
{
/// <summary>
/// Try to open the work item. If an except is thrown then log and suppress it.
/// </summary>
/// <param name="workItem"></param>
public static void TryOpen(this WorkItem workItem)
{
try
{
workItem.Open();
}
catch (Exception e)
{
MiscHelpers.LogMessage(String.Format("Unable to open work item '{0}'\nException: {1}", workItem.Id.ToString(), e.Message), true);
}
}
/// <summary>
/// Used to convert a field to a number. If anything goes wrong then 0 is returned.
/// </summary>
/// <param name="workItem"></param>
/// <param name="fieldName">The name of the field to be retrieved</param>
/// <param name="defaultValue">Value to be returned if something goes wrong.</param>
/// <returns></returns>
public static TType GetField<TType>(this WorkItem workItem, string fieldName, TType defaultValue)
{
try
{
TType convertedValue = (TType) workItem[fieldName];
return convertedValue;
}
catch (Exception)
{
return defaultValue;
}
}
/// <summary>
/// Gets the workItem's parent from the list (if it is in there) or it will load
/// it from the tfs store. We want to use the list if we can so that we can save only
/// one time. (if not we could get conflicts in TFS for several updates to the same item.
/// </summary>
public static WorkItem GetParentFromListOrStore(this WorkItem childItem, IEnumerable<WorkItem> workItemList, Store store)
{
WorkItem parent;
// See if the parent work item is already in our workItemList (if so just use that one).
int targetWorkItemId = (from WorkItemLink workItemLink in childItem.WorkItemLinks
where workItemLink.LinkTypeEnd.Name == "Parent"
select workItemLink.TargetId).FirstOrDefault();
parent = workItemList.Where(workItem => workItem.Id == targetWorkItemId).FirstOrDefault();
if (parent == null)
{
parent = (from WorkItemLink workItemLink in childItem.WorkItemLinks
where workItemLink.LinkTypeEnd.Name == "Parent"
select store.Access.GetWorkItem(workItemLink.TargetId)).FirstOrDefault();
}
return parent;
}
/// <summary>
/// Gets the children of a work item
/// </summary>
public static IEnumerable<WorkItem> GetChildrenFromListOrStore(this WorkItem parent, IEnumerable<WorkItem> workItemList, Store store)
{
List<WorkItem> decendants = new List<WorkItem>();
// Go through all the links for the work item passed in (parent)
foreach (WorkItemLink link in parent.WorkItemLinks)
{
WorkItem childWorkItem;
// Find all the child links
if (link.LinkTypeEnd.Name == "Child")
{
// If this child link is in the list of items then add it to the result set
if (workItemList.Select(x=>x.Id).Contains(link.TargetId))
{
WorkItemLink linkClosure = link;
childWorkItem = workItemList.Where(x => x.Id == linkClosure.TargetId).FirstOrDefault();
}
// if the item was not in our list then get it from the store
else
{
childWorkItem = store.Access.GetWorkItem(link.TargetId);
}
decendants.Add(childWorkItem);
}
}
return decendants;
}
public static void TransitionToState(this WorkItem workItem, string state, string commentPrefix)
{
// Set the sourceWorkItem's state so that it is clear that it has been moved.
string originalState = (string)workItem.Fields["State"].Value;
// Try to set the state of the source work item to the "Deleted/Moved" state (whatever is defined in the file).
// We need an open work item to set the state
workItem.TryOpen();
// See if we can go directly to the planned state.
workItem.Fields["State"].Value = state;
if (workItem.Fields["State"].Status != FieldStatus.Valid)
{
// Revert back to the original value and start searching for a way to our "MovedState"
workItem.Fields["State"].Value = workItem.Fields["State"].OriginalValue;
// If we can't then try to go from the current state to another state. Saving each time till we get to where we are going.
foreach (string curState in workItem.Type.FindNextState((string)workItem.Fields["State"].Value, state))
{
string comment;
if (curState == state)
comment = commentPrefix + Environment.NewLine + " State changed to " + state;
else
comment = commentPrefix + Environment.NewLine + " State changed to " + curState + " as part of move toward a state of " + state;
bool success = ChangeWorkItemState(workItem, originalState, curState, comment);
// If we could not do the incremental state change then we are done. We will have to go back to the orginal...
if (!success)
break;
}
}
else
{
// Just save it off if we can.
string comment = commentPrefix + "\n State changed to " + state;
ChangeWorkItemState(workItem, originalState, state, comment);
}
}
private static bool ChangeWorkItemState(this WorkItem workItem, string orginalSourceState, string destState, String comment)
{
// Try to save the new state. If that fails then we also go back to the original state.
try
{
workItem.TryOpen();
workItem.Fields["State"].Value = destState;
workItem.History = comment;
if(TFSAggregatorSettings.LoggingIsEnabled) MiscHelpers.LogMessage(String.Format("{0}{0}{0}{0}{0}Attempting to move {1} [{2}] from {3} state to {4} state. (ChangeWorkItemState)", " ", workItem.Type.Name, workItem.Id, orginalSourceState, destState));
if (workItem.IsValid())
{
if (TFSAggregatorSettings.LoggingIsEnabled) MiscHelpers.LogMessage(String.Format("{0}{0}{0}{0}{0}{0}{1} [{2}] is valid to save.", " ", workItem.Type.Name, workItem.Id));
}
else
{
if (TFSAggregatorSettings.LoggingIsEnabled) MiscHelpers.LogMessage(String.Format("{0}{0}{0}{0}{0}{0}The work item is invalid in the {1} state. Invalid fields: {2}", " ", destState, MiscHelpers.GetInvalidWorkItemFieldsList(workItem).ToString()));
}
workItem.Save();
return true;
}
catch (Exception)
{
// Revert back to the original value.
workItem.Fields["State"].Value = orginalSourceState;
return false;
}
}
/// <summary>
/// Used to find the next state on our way to a destination state.
/// (Meaning if we are going from a "Not-Started" to a "Done" state,
/// we usually have to hit a "in progress" state first.
/// </summary>
/// <param name="wiType"></param>
/// <param name="fromState"></param>
/// <param name="toState"></param>
/// <returns></returns>
public static IEnumerable<string> FindNextState(this WorkItemType wiType, string fromState, string toState)
{
var map = new Dictionary<string, string>();
var edges = wiType.GetTransitions().ToDictionary(i => i.From, i => i.To);
var q = new Queue<string>();
map.Add(fromState, null);
q.Enqueue(fromState);
while (q.Count > 0)
{
var current = q.Dequeue();
foreach (var s in edges[current])
{
if (!map.ContainsKey(s))
{
map.Add(s, current);
if (s == toState)
{
var result = new Stack<string>();
var thisNode = s;
do
{
result.Push(thisNode);
thisNode = map[thisNode];
} while (thisNode != fromState);
while (result.Count > 0)
yield return result.Pop();
yield break;
}
q.Enqueue(s);
}
}
}
// no path exists
}
private static readonly Dictionary<WorkItemType, List<Transition>> _allTransistions = new Dictionary<WorkItemType, List<Transition>>();
/// <summary>
/// Deprecated
/// Get the transitions for this <see cref="WorkItemType"/>
/// </summary>
/// <param name="workItemType"></param>
/// <returns></returns>
public static List<Transition> GetTransitions(this WorkItemType workItemType)
{
List<Transition> currentTransistions;
// See if this WorkItemType has already had it's transitions figured out.
_allTransistions.TryGetValue(workItemType, out currentTransistions);
if (currentTransistions != null)
return currentTransistions;
// Get this worktype type as xml
XmlDocument workItemTypeXml = workItemType.Export(false);
// Create a dictionary to allow us to look up the "to" state using a "from" state.
var newTransistions = new List<Transition>();
// get the transitions node.
XmlNodeList transitionsList = workItemTypeXml.GetElementsByTagName("TRANSITIONS");
// As there is only one transitions item we can just get the first
XmlNode transitions = transitionsList[0];
// Iterate all the transitions
foreach (XmlNode transitionXML in transitions)
{
// See if we have this from state already.
string fromState = transitionXML.Attributes["from"].Value;
Transition transition = newTransistions.Find(trans => trans.From == fromState);
if (transition != null)
{
transition.To.Add(transitionXML.Attributes["to"].Value);
}
// If we could not find this state already then add it.
else
{
// save off the transition (from first so we can look up state progression.
newTransistions.Add(new Transition
{
From = transitionXML.Attributes["from"].Value,
To = new List<string> { transitionXML.Attributes["to"].Value }
});
}
}
// Save off this transition so we don't do it again if it is needed.
_allTransistions.Add(workItemType, newTransistions);
return newTransistions;
}
}
}