TFS2010 Object Model - Gestione dei WorkItems

Scritto da  Massimo Bonanni il mercoledì 9 marzo 2011
Linguaggio: VB   •  Framework: 4.0   •  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: 

Figura1

otteniamo la seguente lista:

Figura2

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.

Figura3

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

 
x