TFS2010 Object Model - Gestione dei WorkItems
Scritto da
Massimo Bonanni
il
mercoledì 9 marzo 2011
Linguaggio:
•
Framework:
•
Livello: 200
Download sorgenti
Download pdf
In questo articolo esamineremo la gestione dei work items, cioè
le modalità con cui possiamo creare o modificare work items
all'interno di un Team Foundation Server utilizzando l'object model
messoci a disposizione.
Nel secondo articolo della serie (TFS Object
Model - Anatomia di un WorkItem) abbiamo esaminato la struttura
della classe che si occupa di modellare il generico Work Item di
TFS e quindi daremo per acquisita tale struttura
Prima di addentrarci nel vivo della gestione effettiva del Work
Item analizziamo come sia possibile reciuperare dal server di TFS
le istanze che ci occorranno per valorizzare opportunamente un
eventuale Work Item come, ad esempio, le aree, le tipologie di work
item, etc., etc.
Recuperare le aree di progetto
La classe WorkItem di TFS Object Model (namespace
Microsoft.TeamFoundation.WorkItemTracking.Client) espone due
proprietà che sono relative all'area a cui il work item è associato
ed in particolare le proprietà AreaId e AreaPath che contengono ,
rispettivamente, il valore dell'id dell'area associata ed il path
(nella forma "Progetto\Area\SottoArea").
E' evidente, quindi che, nel momento in cui vogliamo valorizzare
tali proprietà (sia in fase di creazione del work item che in fase
di modifica), dovremmo essere in grado di recuperare la lista di
queste dal server.
Per recuperare le aree definite in un progetto utilizziamo il
WorkItemStore (già esaminato nei precedenti articoli) tramite il
quale siamo in grado di recuperare il progetto che ci interessa, il
quale ci fornirà una proprietà, chiamata AreaRootNodes che fornisce
una collezione di nodi che rappresentano le aree.
Diciamo subito che il progetto costituisce l'area root (pur non
essendo esplicitamente un'area) e che le aree possono essere
definite in maniera gerarchica.
La collezione RootAreaNodes è di tipo NodeCollection e ogni nodo in
essa contenuto è capace di avere dei nodi sottostanti (proprietà
ChildNodes).
Per questo motivo, se vogliamo recuperare tutte le aree definite
nel progetto, potremmo essere costretti ad implementare una
funzione ricorsiva che scandisca l'albero dei nodi.
Un esempio di funzione che, a partire dal nome di una collection
e di un progetto, recupera l'elenco delle aree (senza mantenere la
gerarchia) è la seguente:
Public Function GetAreas(ByVal collectionName As String, ByVal projectName As String) As IEnumerable(Of Node)
Dim retList As List(Of Node) = Nothing
If TFSInstance IsNot Nothing Then
If Not String.IsNullOrWhiteSpace(projectName) Then
Try
Dim workItemService = CType(TFSInstance.GetService(Of WorkItemStore)(), WorkItemStore)
Dim prj = (From p In workItemService.Projects.OfType(Of Microsoft.TeamFoundation.WorkItemTracking.Client.Project)()
Where p.Name = projectName
Select p).FirstOrDefault()
If prj IsNot Nothing Then
retList = New List(Of Node)
ExtractNodes(prj.AreaRootNodes, retList)
End If
Catch ex As Exception
Throw
End Try
Else
Throw New ArgumentNullException()
End If
Else
Throw New Exception("Collezione non esistente")
End If
Return retList
End Function
Private Sub ExtractNodes(ByVal nodeCollection As NodeCollection, ByRef retList As List(Of Node))
For Each node In nodeCollection.OfType(Of Microsoft.TeamFoundation.WorkItemTracking.Client.Node)()
retList.Add(New Node With {.Identifier = node.Id,
.Name = node.Name,
.Path = node.Path})
If node.HasChildNodes Then
ExtractNodes(node.ChildNodes, retList)
End If
Next
End Sub
La proprietà TFSInstance è una istanza di
TfsTeamProjectCollection che la nostra classe di servizio
istanzierà al momento del login:
Public Overrides Function Login() As Boolean
Dim retval = False
If TFSInstance IsNot Nothing Then
TFSInstance.Dispose()
TFSInstance = Nothing
End If
Try
TFSInstance = TfsTeamProjectCollectionFactory.GetTeamProjectCollection(GetCollectionUri(Me.ServerURL, CollectionName),
New UICredentialsProvider())
TFSInstance.Authenticate()
retval = True
Catch ex As System.Exception
retval = False
TFSInstance = Nothing
End Try
Return retval
End Function
La classe Node espone Id, Path e Name del nodo memorizzato (in
questo caso dell'area). La funzione ricorsiva ExtractNodes permette
di costruire un array (per scelta piatto) in cui sono inserite
tutte le aree disponibili.
Ad esempio, se abbiamo la seguente definizione di
aree:

otteniamo la seguente lista:

I valori di Id e Path sono quelli che possiamo utilizzare per
valorizzare le proprietà del work item.
Recuperare le iterazioni di progetto
In maniera del tutto analoga a quanto fatto per le aree di
progetto, possiamo recuperare le iterazioni utilizzando la
proprietà IterationRootNodes della classe TeamProject.
Poichè questa proprietà è di tipo NodeCollection, possiamo
riutilizzare la funzione scritta per recuperare le aree:
Public Function GetIterations(ByVal collectionName As String, ByVal projectName As String) As IEnumerable(Of Node)
Dim retList As List(Of Node) = Nothing
If TFSInstance IsNot Nothing Then
If Not String.IsNullOrWhiteSpace(projectName) Then
Try
Dim workItemService = CType(TFSInstance.GetService(Of WorkItemStore)(), WorkItemStore)
Dim prj = (From p In workItemService.Projects.OfType(Of Microsoft.TeamFoundation.WorkItemTracking.Client.Project)()
Where p.Name = projectName
Select p).FirstOrDefault()
If prj IsNot Nothing Then
retList = New List(Of Node)
ExtractNodes(prj.IterationRootNodes, retList)
End If
Catch ex As Exception
Throw
End Try
Else
Throw New ArgumentNullException()
End If
Else
Throw New Exception("Collezione non esistente")
End If
Return retList
End Function
Recuperare i WorkItemTypes
Per poter creare un WorkItem è necessario definire la tipologia
dello stesso e, come è noto, tale tipologia può cambiare in base al
template di progetto che si sta utilizzando (Scrum ha dei tipi di
work item differenti da MSF Agile).
Dobbiamo, quindi, recuperare la lista dei tipi di work item
definiti nel progetto. Per fare ciò utilizziamo la proprietà
WorkItemTypes che contiene un oggetto di classe
WorkItemTypeCollection. Quest'ultima è una collezione in sola
lettura di oggetti di tipo WorkItemType.

La classe WorkItemType contiene i dati descrittivi del
WorkItemType (descrizione, nome e testo per la visualizzazione), i
riferimenti al progetto a cui appartiene, al servizio WorkItemStore
che lo gestisce e ad una collezione che contiene la definizione di
tutti i campi previsti per il work item.
Public Function GetWorkItemTypes(ByVal collectionName As String, ByVal projectName As String) As IEnumerable(Of WorkItemType)
Dim retList As List(Of WorkItemType) = Nothing
If TFSInstance IsNot Nothing Then
If Not String.IsNullOrWhiteSpace(projectName) Then
Try
Dim workItemService = CType(TFSInstance.GetService(Of WorkItemStore)(), WorkItemStore)
Dim prj = (From p In workItemService.Projects.OfType(Of Microsoft.TeamFoundation.WorkItemTracking.Client.Project)()
Where p.Name = projectName
Select p).FirstOrDefault()
If prj IsNot Nothing Then
retList = (From i In prj.WorkItemTypes.OfType(Of Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType)()
Select New WorkItemType() With {.Name = i.Name,
.Description = i.Description,
.Identifier = i.Name,
.DisplayText = i.DisplayForm}).ToList()
End If
Catch ex As Exception
Throw
End Try
Else
Throw New ArgumentNullException()
End If
Else
Throw New Exception("Collezione non esistente")
End If
Return retList
End Function
Per poter recuperare la lista dei campi previsti per un
determinate WorkItemType (perchè, ad esempio, vogliamo generare
dinamicamente l'interfaccia grafica che l'utente utilizzerà per
l'inserimento dei dati), possiamo sfruttare la collezione
FieldDefinitionCollection esposta dalla proprietà FieldDefinitions
del work item type.
La seguente funzione recupera la definizione dei campi a partire
dal nome del work item type:
Public Function GetWorkItemFields(ByVal projectName As String,
ByVal workItemTypeName As String) As IEnumerable(Of FieldDefinition)
Dim retList As List(Of FieldDefinition) = Nothing
If TFSInstance IsNot Nothing Then
If Not String.IsNullOrWhiteSpace(projectName) _
AndAlso Not String.IsNullOrWhiteSpace(workItemTypeName) Then
Try
Dim workItemService = CType(TFSInstance.GetService(Of WorkItemStore)(), WorkItemStore)
Dim prj = (From p In workItemService.Projects.OfType(Of Microsoft.TeamFoundation.WorkItemTracking.Client.Project)()
Where p.Name = projectName
Select p).FirstOrDefault()
If prj IsNot Nothing Then
Dim wiType = (From i In prj.WorkItemTypes.OfType(Of Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType)()
Where i.Name = workItemTypeName
Select i).FirstOrDefault()
If wiType IsNot Nothing Then
retList = (From d In wiType.FieldDefinitions.OfType(Of FieldDefinition)()
Select d).ToList()
Else
Throw New ArgumentOutOfRangeException()
End If
End If
Catch ex As Exception
Throw
End Try
Else
Throw New ArgumentNullException()
End If
Else
Throw New Exception("Collezione non esistente")
End If
Return retList
End Function
Creazione di un nuovo WorkItem
A questo punto abbiamo tutti gli ingredient per poter creare un
nuovo workitem da codice.
Per creare un nuovo WorkItem dobbiamo utilizzare la classe
WorkItemType che ci mette a disposizione il metodo NewWorkItem la
cui funzione è proprio quella di creare una istanza dell'oggetto
WorklItem di tipo WorkItemType.
La seguente funzione crea un WorkItem a partire dal nome
workitemtype:
Public Function NewWorkItem(ByVal projectName As String,
ByVal workItemTypeName As String) As Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem
Dim retval As Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem = Nothing
If TFSInstance IsNot Nothing Then
If Not String.IsNullOrWhiteSpace(projectName) _
AndAlso Not String.IsNullOrWhiteSpace(workItemTypeName) Then
Try
Dim workItemService = CType(TFSInstance.GetService(Of WorkItemStore)(), WorkItemStore)
Dim prj = (From p In workItemService.Projects.OfType(Of Microsoft.TeamFoundation.WorkItemTracking.Client.Project)()
Where p.Name = projectName
Select p).FirstOrDefault()
If prj IsNot Nothing Then
Dim wiType = (From i In prj.WorkItemTypes.OfType(Of Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItemType)()
Where i.Name = workItemTypeName
Select i).FirstOrDefault()
If wiType IsNot Nothing Then
retval = wiType.NewWorkItem()
Else
Throw New ArgumentOutOfRangeException()
End If
End If
Catch ex As Exception
Throw
End Try
Else
Throw New ArgumentNullException()
End If
Else
Throw New Exception("Collezione non esistente")
End If
Return retval
End Function
Una volta che abbiamo il WorkItem possiamo valorizzare i suoi
campi.
I campi del WorkItem sono memorizzati nella collezione Fields
e potrebbero essere non tutti modificabili (in generale la
possibilità di modificare una proprietà del Work Item dipende dallo
stato del Work Item stesso).
Per poter verificare se il campo può essere modificato facciamo
ricorso alla properietà IsEditable del campo stesso.
Ad esempio, se volessimo impostare il campo Assigned To del work
Item, potremmo scrivere:
If wi.Fields("Assigned To").IsEditable Then
wi.Fields("Assigned To").Value = "Giuseppe Verdi"
End If
Nel momento in cui il Work Item è stato creato e le proprietà
valorizzate, possiamo verificare che il work item stesso sia valido
cioè che tutte le proprietà impostate siano compatibili con i
possibili valori accettate da queste ultime.
Abbiamo due possibili modalità per verificare che un work item
sia correttamente valorizzato o meno:
- Il metodo IsValid che restituisce true solo se tutte le
proprietà impostate nel work item sono congruenti con i possibili
valori che queste possono assumere;
- Il metodo Validate che ci permette non solo di controllare la
validità ma anche di ottenere gli eventuali errori che si sono
verificati.
Il work item creato ed eventualmente modificato rimane in
memoria fino a che non viene eseguito il metodo Save.
Questo metodo permette di inviare tutte le modifiche eseguite al
server.
Se sono presenti eventuali errori di validazione (perchè non si
è eseguito preventivamente IsValid o Validate), allora viene
sollevata un eccezione di tipo ValidationException.
Un esempio di come utilizzare il metodo Save è il seguente:
Public Function SaveWorkItem(ByVal workItem As Microsoft.TeamFoundation.WorkItemTracking.Client.WorkItem) As Boolean
Dim retVal = False
If workItem IsNot Nothing Then
If workItem.IsValid() Then
Try
workItem.Save()
retVal = True
Catch ex As Exception
Throw
End Try
End If
Else
Throw New ArgumentNullException()
End If
Return retVal
End Function
Modifica di un WorkItem
Utilizzando i concetti esposti nel precedente articolo della serie
possiamo recuperare un WorkItem e permettere all'utente di
modificarlo per poi salvarlo nel nostro TFS.
Supponendo di aver recuperato il WorkItem (possiamo farlo
attraverso una query WIQL oppure con il metodo GetWorkItem del
WorkItemStore), possiamo modificare le proprietà dello stesso in
maniera analoga a quanto visto per la creazione di un nuovo
WorkItem.
Ancora una volta possiamo basarci sulla proprietà Fileds per
avere a disposizione i possibili valori avvettati in un campo e
sulla proprietà IsEditable per sapere se il campo del work item è
modificabile nello stato in cui si trova.
Di nuovo, una volta che abbiamo modificato il workitem, possiamo
salvare lo stesso utilizzando il metodo Save in maniera del tutto
analoga a quanto visto in precedenza.
Riferimenti
[1] Team Foundation Server Architecture: http://msdn.microsoft.com/en-us/library/ms252473.aspx
[2] Extending Team Foundation: http://msdn.microsoft.com/en-us/library/bb130146.aspx
[3] Team Foundation Server 2010 SDK: http://code.msdn.microsoft.com/TfsSdk
[4] Team Foundation Server Team Blog: http://blogs.msdn.com/b/team_foundation/
Tags: Team Foundation,Team Foundation Server