Aggiungere un widget del dashboard

Azure DevOps Services | Azure DevOps Server 2022 - Azure DevOps Server 2019

I widget in un dashboard vengono implementati come contributi nel framework di estensione. Una singola estensione può avere più contributi. Informazioni su come creare un'estensione con più widget come contributi.

Questo articolo è suddiviso in tre parti, ognuna delle quali si basa sull'elemento precedente, iniziando con un widget semplice e terminando con un widget completo.

Suggerimento

Vedere la documentazione più recente sullo sviluppo di estensioni con Azure DevOps Extension SDK.

Preparazione e configurazione necessaria per questa esercitazione

Per creare estensioni per Azure DevOps o TFS, sono necessari alcuni strumenti e software prerequisiti:

Conoscenza: per lo sviluppo di widget è necessaria una certa conoscenza di JavaScript, HTML e CSS.

  • Un'organizzazione in Azure DevOps per l'installazione e il test del widget. Altre informazioni sono disponibili qui
  • Editor di testo. Per molte delle esercitazioni, è stato usato Visual Studio Code, che può essere scaricato qui
  • La versione più recente del nodo, che può essere scaricata qui
  • Interfaccia della riga di comando multipiattaforma per Azure DevOps (tfx-cli) per creare un pacchetto delle estensioni.
    • Tfx-cli può essere installato usando npm, un componente di Node.js eseguendo npm i -g tfx-cli
  • Home directory per il progetto. Questa directory viene definita in home tutta l'esercitazione.

Struttura dei file di estensione:

|--- README.md
|--- sdk    
    |--- node_modules           
    |--- scripts
        |--- VSS.SDK.min.js       
|--- img                        
    |--- logo.png                           
|--- scripts                        
|--- hello-world.html               // html page to be used for your widget  
|--- vss-extension.json             // extension's manifest

Informazioni disponibili nell'esercitazione

  1. La prima parte di questa guida illustra come creare un nuovo widget, che stampa un semplice messaggio "Hello World".
  2. La seconda parte si basa sulla prima aggiungendo una chiamata a un'API REST di Azure DevOps.
  3. La terza parte spiega come aggiungere la configurazione al widget.

Nota

Se hai fretta e vuoi mettere subito le mani sul codice, puoi scaricare gli esempi qui. Dopo il download, passare alla widgets cartella, quindi seguire il passaggio 6 e il passaggio 7 direttamente per pubblicare l'estensione di esempio con i tre widget di esempio di diverse complessità.

Introduzione ad alcuni stili di base per i widget forniti per te e alcune indicazioni sulla struttura dei widget.

Parte 1: Hello World

Questa parte presenta un widget che stampa "Hello World" usando JavaScript.

Overview dashboard with a sample widget

Passaggio 1: Ottenere l'SDK client - VSS.SDK.min.js

Lo script dell'SDK principale, VSS.SDK.min.js, consente alle estensioni Web di comunicare con il frame di Azure DevOps host. Lo script esegue operazioni come l'inizializzazione, la notifica dell'estensione viene caricata o il recupero del contesto sulla pagina corrente. Ottenere il file dell'SDK VSS.SDK.min.js client e aggiungerlo all'app Web. Posizionarlo nella home/sdk/scripts cartella .

Usare il comando 'npm install' per recuperare l'SDK:

npm install vss-web-extension-sdk

Per altre informazioni sull'SDK, visitare la pagina GitHub dell'SDK client.

Passaggio 2: Pagina HTML - hello-world.html

La pagina HTML è l'associazione che contiene il layout e include riferimenti a CSS e JavaScript. È possibile assegnare un nome a questo file, ma assicurarsi di aggiornare tutti i riferimenti a hello-world con il nome usato.

Il widget è basato su HTML ed è ospitato in un iframe. Aggiungere il codice HTML seguente in hello-world.html. Aggiungere il riferimento obbligatorio al VSS.SDK.min.js file e includere un h2 elemento in , che viene aggiornato con la stringa Hello World nel passaggio successivo.

    <!DOCTYPE html>
    <html>
        <head>          
            <script src="sdk/scripts/VSS.SDK.min.js"></script>              
        </head>
        <body>
            <div class="widget">
                <h2 class="title"></h2>
            </div>
        </body>
    </html>

Anche se si usa un file HTML, la maggior parte degli elementi head HTML diversi dallo script e dal collegamento viene ignorata dal framework.

Passaggio 3: JavaScript

Per eseguire il rendering del contenuto nel widget viene usato JavaScript. In questo articolo viene eseguito il wrapping di tutto il codice JavaScript all'interno di un &lt;script&gt; elemento nel file HTML. È possibile scegliere di avere questo codice in un file JavaScript separato e farvi riferimento nel file HTML. Il codice esegue il rendering del contenuto. Questo codice JavaScript inizializza anche VSS SDK, esegue il mapping del codice per il widget al nome del widget e invia una notifica al framework di estensione delle operazioni riuscite o non riuscite del widget. In questo caso, di seguito è riportato il codice che stampa "Hello World" nel widget. Aggiungere questo script elemento nell'oggetto head del codice HTML.

    <script type="text/javascript">
        VSS.init({                        
            explicitNotifyLoaded: true,
            usePlatformStyles: true
        });

        VSS.require("TFS/Dashboards/WidgetHelpers", function (WidgetHelpers) {
            WidgetHelpers.IncludeWidgetStyles();
            VSS.register("HelloWorldWidget", function () {                
                return {
                    load: function (widgetSettings) {
                        var $title = $('h2.title');
                        $title.text('Hello World');

                        return WidgetHelpers.WidgetStatusHelper.Success();
                    }
                }
            });
            VSS.notifyLoadSucceeded();
        });
    </script>

VSS.init inizializza l'handshake tra l'iframe che ospita il widget e il frame host. explicitNotifyLoaded: true Passiamo in modo che il widget possa notificare in modo esplicito all'host al termine del caricamento. Questo controllo consente di notificare il completamento del carico dopo aver verificato che i moduli dipendenti vengano caricati. Viene passato usePlatformStyles: true in modo che gli stili di base di Azure DevOps per gli elementi HTML (ad esempio corpo, div e così via) possano essere usati dal widget. Se il widget preferisce non usare questi stili, è possibile passare usePlatformStyles: false.

VSS.require viene usato per caricare le librerie di script VSS necessarie. Una chiamata a questo metodo carica automaticamente librerie generali come JQuery e JQueryUI. In questo caso, si dipende dalla libreria WidgetHelpers, che viene usata per comunicare lo stato del widget al framework del widget. Quindi, passiamo il nome TFS/Dashboards/WidgetHelpers del modulo corrispondente e un callback a VSS.require. Il callback viene chiamato dopo il caricamento del modulo. Il callback include il resto del codice JavaScript necessario per il widget. Al termine del callback, viene chiamato VSS.notifyLoadSucceeded per notificare il completamento del caricamento.

WidgetHelpers.IncludeWidgetStyles include un foglio di stile con alcuni css di base per iniziare. Assicurarsi di eseguire il wrapping del contenuto all'interno di un elemento HTML con classe widget per usare questi stili.

VSS.register viene usato per eseguire il mapping di una funzione in JavaScript, che identifica in modo univoco il widget tra i diversi contributi nell'estensione. Il nome deve corrispondere a id che identifica il contributo come descritto nel passaggio 5. Per i widget, la funzione passata a VSS.register deve restituire un oggetto che soddisfa il IWidget contratto, ad esempio, l'oggetto restituito deve avere una proprietà di caricamento il cui valore è un'altra funzione con la logica di base per eseguire il rendering del widget. In questo caso, è necessario aggiornare il testo dell'elemento h2 in "Hello World". Questa funzione viene chiamata quando il framework del widget crea un'istanza del widget. Viene usato da WidgetStatusHelper WidgetHelpers per restituire l'oggetto WidgetStatus come esito positivo.

Avviso

Se il nome usato per registrare il widget non corrisponde all'ID per il contributo nel manifesto, il widget funziona in modo imprevisto.

Deve vss-extension.json sempre trovarsi nella radice della cartella (in questa guida, HelloWorld). Per tutti gli altri file, è possibile inserirli in qualsiasi struttura desiderata all'interno della cartella, è sufficiente assicurarsi di aggiornare i riferimenti in modo appropriato nei file HTML e nel vss-extension.json manifesto.

Passaggio 4: Logo dell'estensione: logo.png

Il logo viene visualizzato nel Marketplace e nel catalogo dei widget dopo che un utente installa l'estensione.

È necessaria un'icona del catalogo 98 px x 98 px. Scegliere un'immagine, denominarla logo.pnge inserirla nella img cartella .

Per supportare TFS 2015 Update 3, è necessaria un'immagine aggiuntiva di 330 px x 160 px. Questa immagine di anteprima è illustrata in questo catalogo. Scegliere un'immagine, denominarla preview.pnge inserirla nella img cartella come prima.

È possibile denominare queste immagini, purché il manifesto dell'estensione nel passaggio successivo venga aggiornato con i nomi usati.

Passaggio 5: Manifesto dell'estensione: vss-extension.json

  • Ogni estensione deve avere un file manifesto di estensione
  • Leggere il riferimento al manifesto dell'estensione
  • Altre informazioni sui punti di contributo nei punti di estendibilità

Creare un file JSON (vss-extension.jsonad esempio) nella home directory con il contenuto seguente:

    {
        "manifestVersion": 1,
        "id": "vsts-extensions-myExtensions",
        "version": "1.0.0",
        "name": "My First Set of Widgets",
        "description": "Samples containing different widgets extending dashboards",
        "publisher": "fabrikam",
        "categories": ["Azure Boards"],
        "targets": [
            {
                "id": "Microsoft.VisualStudio.Services"
            }
        ],
        "icons": {
            "default": "img/logo.png"
        },
        "contributions": [
            {
                "id": "HelloWorldWidget",
                "type": "ms.vss-dashboards-web.widget",
                "targets": [
                    "ms.vss-dashboards-web.widget-catalog"
                ],
                "properties": {
                    "name": "Hello World Widget",
                    "description": "My first widget",
                    "catalogIconUrl": "img/CatalogIcon.png",
                    "previewImageUrl": "img/preview.png",                            
                    "uri": "hello-world.html",
                    "supportedSizes": [
                         {
                                "rowSpan": 1,
                                "columnSpan": 2
                            }
                        ],
                    "supportedScopes": ["project_team"]
                }
            }
        ],
        "files": [
            {
                "path": "hello-world.html", "addressable": true
            },
            {
                "path": "sdk/scripts", "addressable": true
            },
            {
                "path": "img", "addressable": true
            }
        ]
    }

Per altre informazioni sugli attributi obbligatori, vedere informazioni di riferimento sul manifesto dell'estensione

Nota

Il server di pubblicazione deve essere modificato nel nome dell'editore. Per creare un server di pubblicazione, visitare Pacchetto/Pubblicazione/Installazione.

Icone

La stanza delle icone specifica il percorso dell'icona dell'estensione nel manifesto.

Contributi

Ogni voce di contributo definisce le proprietà.

  • ID per identificare il contributo. Questo ID deve essere univoco all'interno di un'estensione. Questo ID deve corrispondere al nome usato nel passaggio 3 per registrare il widget.
  • Tipo di contributo. Per tutti i widget, il tipo deve essere ms.vss-dashboards-web.widget.
  • Matrice di destinazioni a cui contribuisce il contributo. Per tutti i widget, la destinazione deve essere [ms.vss-dashboards-web.widget-catalog].
  • Le proprietà sono oggetti che includono proprietà per il tipo di contributo. Per i widget, le proprietà seguenti sono obbligatorie.
Proprietà Descrizione
name Nome del widget da visualizzare nel catalogo dei widget.
description Descrizione del widget da visualizzare nel catalogo dei widget.
catalogIconUrl Percorso relativo dell'icona del catalogo aggiunta nel passaggio 4 da visualizzare nel catalogo dei widget. L'immagine deve essere 98 px x 98 px. Se è stata usata una struttura di cartelle diversa o un nome di file diverso, specificare il percorso relativo appropriato qui.
previewImageUrl Percorso relativo dell'immagine di anteprima aggiunta nel passaggio 4 per la visualizzazione nel catalogo dei widget solo per TFS 2015 Update 3. L'immagine deve essere 330 px x 160 px. Se è stata usata una struttura di cartelle diversa o un nome di file diverso, specificare il percorso relativo appropriato qui.
uri Percorso relativo del file HTML aggiunto nel passaggio 1. Se è stata usata una struttura di cartelle diversa o un nome di file diverso, specificare il percorso relativo appropriato qui.
supportedSizes Matrice di dimensioni supportate dal widget. Quando un widget supporta più dimensioni, la prima dimensione nella matrice è la dimensione predefinita del widget. Viene widget size specificato per le righe e le colonne occupate dal widget nella griglia del dashboard. Una riga/colonna corrisponde a 160 px. Qualsiasi dimensione superiore a 1x1 ottiene un ulteriore 10 px che rappresenta la barra tra i widget. Ad esempio, un widget 3x2 è 160*3+10*2 largo e 160*2+10*1 alto. La dimensione massima supportata è 4x4.
supportedScopes Al momento, supportiamo solo i dashboard del team. Il valore deve essere project_team. In futuro, quando sono supportati altri ambiti del dashboard, sono disponibili altre opzioni tra cui scegliere.

File

La stanza dei file indica i file da includere nel pacchetto, ovvero la pagina HTML, gli script, lo script SDK e il logo. Impostare su addressable a true meno che non si includano altri file che non devono essere indirizzabili tramite URL.

Nota

Per altre informazioni sul file manifesto dell'estensione, ad esempio le relative proprietà e le relative operazioni, vedere il riferimento al manifesto dell'estensione.

Passaggio 6: Creare un pacchetto, pubblicare e condividere

Dopo aver scritto l'estensione, il passaggio successivo per inserirlo nel Marketplace consiste nel creare un pacchetto di tutti i file. Tutte le estensioni vengono in pacchetto come file VSIX 2.0 compatibili con estensione vsix: Microsoft fornisce un'interfaccia della riga di comando multipiattaforma per creare un pacchetto dell'estensione.

Ottenere lo strumento di creazione dei pacchetti

È possibile installare o aggiornare l'interfaccia della riga di comando multipiattaforma per Azure DevOps (tfx-cli) usando npm, un componente di Node.js, dalla riga di comando.

npm i -g tfx-cli

Creare il pacchetto dell'estensione

La creazione di pacchetti dell'estensione in un file con estensione vsix è semplice dopo aver creato tfx-cli. Passare alla home directory dell'estensione ed eseguire il comando seguente.

tfx extension create --manifest-globs vss-extension.json

Nota

La versione di un'estensione/integrazione deve essere incrementata a ogni aggiornamento.
Quando si aggiorna un'estensione esistente, aggiornare la versione nel manifesto o passare l'opzione della --rev-version riga di comando. In questo modo viene incrementato il numero di versione della patch dell'estensione e viene salvata la nuova versione nel manifesto.

Dopo aver creato l'estensione in pacchetto in un file con estensione vsix, è possibile pubblicare l'estensione nel Marketplace.

Creare un server di pubblicazione per l'estensione

Tutte le estensioni, incluse le estensioni di Microsoft, vengono identificate come fornite da un editore. Se non si è già membri di un server di pubblicazione esistente, ne verrà creato uno.

  1. Accedere al portale di pubblicazione di Visual Studio Marketplace
  2. Se non si è già membri di un server di pubblicazione esistente, verrà richiesto di creare un server di pubblicazione. Se non viene richiesto di creare un editore, scorrere verso il basso fino alla fine della pagina e selezionare Pubblica estensioni sotto Siti correlati.
    • Specificare un identificatore per il server di pubblicazione, ad esempio: mycompany-myteam
      • L'identificatore viene usato come valore per l'attributo publisher nel file manifesto delle estensioni.
    • Specificare un nome visualizzato per il server di pubblicazione, ad esempio: My Team
  3. Esaminare il Contratto di pubblicazione del Marketplace e selezionare Crea

Ora il server di pubblicazione è definito. In una versione futura è possibile concedere le autorizzazioni per visualizzare e gestire le estensioni dell'editore. È facile e più sicuro per i team e le organizzazioni pubblicare estensioni in un editore comune, ma senza la necessità di condividere un set di credenziali in un set di utenti.

Aggiornare il vss-extension.json file manifesto negli esempi per sostituire l'ID editore fittizio con l'ID fabrikam editore.

Pubblicare e condividere l'estensione

Dopo aver creato un editore, è ora possibile caricare l'estensione nel Marketplace.

  1. Trovare il pulsante Carica nuova estensione, passare al file con estensione vsix in pacchetto e selezionare Carica.

È anche possibile caricare l'estensione tramite la riga di tfx extension create comando usando il tfx extension publish comando anziché per creare un pacchetto e pubblicare l'estensione in un unico passaggio. Facoltativamente, è possibile usare --share-with per condividere l'estensione con uno o più account dopo la pubblicazione. È necessario anche un token di accesso personale.

tfx extension publish --manifest-globs your-manifest.json --share-with yourOrganization

Passaggio 7: Aggiungere il widget dal catalogo

  1. Passare al progetto in Azure DevOps, http://dev.azure.com/{yourOrganization}/{yourProject}

  2. Selezionare Panoramica e quindi Dashboard.

  3. Scegliere Aggiungi un widget.

  4. Evidenziare il widget e quindi selezionare Aggiungi.

    Il widget viene visualizzato nel dashboard.

Parte 2: Hello World con l'API REST di Azure DevOps

I widget possono chiamare qualsiasi API REST in Azure DevOps per interagire con le risorse di Azure DevOps. In questo esempio viene usata l'API REST per WorkItemTracking per recuperare informazioni su una query esistente e visualizzare alcune informazioni sulla query nel widget subito sotto il testo "Hello World".

Overview dashboard with a sample widget using the REST API for WorkItemTracking.

Passaggio 1: HTML

Copiare il file hello-world.html dall'esempio precedente e rinominare la copia in hello-world2.html. La cartella è ora simile alla seguente:

|--- README.md
|--- sdk    
    |--- node_modules           
    |--- scripts
        |--- VSS.SDK.min.js       
|--- img                        
    |--- logo.png                           
|--- scripts                        
|--- hello-world.html               // html page to be used for your widget  
|--- hello-world2.html              // renamed copy of hello-world.html
|--- vss-extension.json             // extension's manifest

Aggiungere un nuovo elemento "div" sotto "h2" per contenere le informazioni sulla query. Aggiornare il nome del widget da 'HelloWorldWidget' a 'HelloWorldWidget2' nella riga in cui si chiama 'VSS.register'. Ciò consente al framework di identificare in modo univoco il widget all'interno dell'estensione.
<!DOCTYPE html>
<html>
    <head>                          
        <script src="sdk/scripts/VSS.SDK.min.js"></script>              
        <script type="text/javascript">
            VSS.init({
                explicitNotifyLoaded: true,
                usePlatformStyles: true
            });

            VSS.require("TFS/Dashboards/WidgetHelpers", function (WidgetHelpers) {
                WidgetHelpers.IncludeWidgetStyles();
                VSS.register("HelloWorldWidget2", function () {                
                    return {
                        load: function (widgetSettings) {
                            var $title = $('h2.title');
                            $title.text('Hello World');

                            return WidgetHelpers.WidgetStatusHelper.Success();
                        }
                    }
                });
                VSS.notifyLoadSucceeded();
            });       
        </script>
    </head>
    <body>
        <div class="widget">
            <h2 class="title"></h2>
            <div id="query-info-container"></div>
        </div>
    </body>
</html>

Passaggio 2: Accedere alle risorse di Azure DevOps

Per abilitare l'accesso alle risorse di Azure DevOps, è necessario specificare gli ambiti nel manifesto dell'estensione. Aggiungere l'ambito vso.work al manifesto.
Questo ambito indica che il widget richiede l'accesso in sola lettura alle query e agli elementi di lavoro. Vedere tutti gli ambiti disponibili qui. Aggiungere quanto segue alla fine del manifesto dell'estensione.

{
    ...,
    "scopes":[
        "vso.work"
    ]
}

Avviso

L'aggiunta o la modifica di ambiti dopo la pubblicazione di un'estensione non è attualmente supportata. Se l'estensione è già stata caricata, rimuoverla dal Marketplace. Passare a Visual Studio Marketplace Publishing Portal, fare clic con il pulsante destro del mouse sull'estensione e selezionare "Rimuovi".

Passaggio 3: Effettuare la chiamata API REST

Sono disponibili molte librerie lato client a cui è possibile accedere tramite l'SDK per effettuare chiamate API REST in Azure DevOps. Queste librerie sono denominate client REST e sono wrapper JavaScript intorno alle chiamate Ajax per tutti gli endpoint lato server disponibili. È possibile usare metodi forniti da questi client invece di scrivere manualmente chiamate Ajax. Questi metodi eseguono il mapping delle risposte API agli oggetti che possono essere utilizzati dal codice.

In questo passaggio si aggiorna la VSS.require chiamata per caricare TFS/WorkItemTracking/RestClient, che fornisce il client REST WorkItemTracking. È possibile usare questo client REST per ottenere informazioni su una query denominata Feedback nella cartella Shared Queries.

All'interno della funzione passata a VSS.registerviene creata una variabile per contenere l'ID progetto corrente. Questa variabile è necessaria per recuperare la query. Viene anche creato un nuovo metodo getQueryInfo per usare il client REST. Questo metodo viene quindi chiamato dal metodo load.

Il metodo getClient fornisce un'istanza del client REST necessario. Il metodo getQuery restituisce la query sottoposta a wrapping in una promessa. L'aggiornamento VSS.require è simile al seguente:

VSS.require(["TFS/Dashboards/WidgetHelpers", "TFS/WorkItemTracking/RestClient"], 
    function (WidgetHelpers, TFS_Wit_WebApi) {
        WidgetHelpers.IncludeWidgetStyles();
        VSS.register("HelloWorldWidget2", function () { 
            var projectId = VSS.getWebContext().project.id;

            var getQueryInfo = function (widgetSettings) {
                // Get a WIT client to make REST calls to Azure DevOps Services
                return TFS_Wit_WebApi.getClient().getQuery(projectId, "Shared Queries/Feedback")
                    .then(function (query) {
                        // Do something with the query

                        return WidgetHelpers.WidgetStatusHelper.Success();
                    }, function (error) {                            
                        return WidgetHelpers.WidgetStatusHelper.Failure(error.message);
                    });
            }

            return {
                load: function (widgetSettings) {
                    // Set your title
                    var $title = $('h2.title');
                    $title.text('Hello World');

                    return getQueryInfo(widgetSettings);
                }
            }
        });
        VSS.notifyLoadSucceeded();
    });

Si noti l'uso del metodo Failure da WidgetStatusHelper. Consente di indicare al framework del widget che si è verificato un errore e sfruttare l'esperienza di errore standard fornita a tutti i widget.

Se la Feedback query Shared Queries non è presente nella cartella , sostituire Shared Queries\Feedback nel codice con il percorso di una query presente nel progetto.

Passaggio 4: Visualizzare la risposta

L'ultimo passaggio consiste nel eseguire il rendering delle informazioni sulla query all'interno del widget. La getQuery funzione restituisce un oggetto di tipo Contracts.QueryHierarchyItem all'interno di una promessa. In questo esempio vengono visualizzati l'ID della query, il nome della query e il nome dell'autore della query nel testo "Hello World". Sostituire il // Do something with the query commento con il codice seguente:

    // Create a list with query details                                
    var $list = $('<ul>');                                
    $list.append($('- ').text("Query Id: " + query.id));
    $list.append($('- ').text("Query Name: " + query.name));
    $list.append($('- ').text("Created By: " + ( query.createdBy? query.createdBy.displayName: "<unknown>" ) ) );                                                            

    // Append the list to the query-info-container
    var $container = $('#query-info-container');
    $container.empty();
    $container.append($list);

L'ultima hello-world2.html è la seguente:

<!DOCTYPE html>
<html>
<head>    
    <script src="sdk/scripts/VSS.SDK.min.js"></script>
    <script type="text/javascript">
        VSS.init({
            explicitNotifyLoaded: true,
            usePlatformStyles: true
        });

        VSS.require(["TFS/Dashboards/WidgetHelpers", "TFS/WorkItemTracking/RestClient"], 
            function (WidgetHelpers, TFS_Wit_WebApi) {
                WidgetHelpers.IncludeWidgetStyles();
                VSS.register("HelloWorldWidget2", function () {                
                    var projectId = VSS.getWebContext().project.id;

                    var getQueryInfo = function (widgetSettings) {
                        // Get a WIT client to make REST calls to Azure DevOps Services
                        return TFS_Wit_WebApi.getClient().getQuery(projectId, "Shared Queries/Feedback")
                            .then(function (query) {
                                // Create a list with query details                                
                                var $list = $('<ul>');
                                $list.append($('- ').text("Query ID: " + query.id));
                                $list.append($('- ').text("Query Name: " + query.name));
                                $list.append($('- ').text("Created By: " + (query.createdBy ? query.createdBy.displayName: "<unknown>") ));

                                // Append the list to the query-info-container
                                var $container = $('#query-info-container');
                                $container.empty();
                                $container.append($list);

                                // Use the widget helper and return success as Widget Status
                                return WidgetHelpers.WidgetStatusHelper.Success();
                            }, function (error) {
                                // Use the widget helper and return failure as Widget Status
                                return WidgetHelpers.WidgetStatusHelper.Failure(error.message);
                            });
                    }

                    return {
                        load: function (widgetSettings) {
                            // Set your title
                            var $title = $('h2.title');
                            $title.text('Hello World');

                            return getQueryInfo(widgetSettings);
                        }
                    }
                });
            VSS.notifyLoadSucceeded();
        });       
    </script>

</head>
<body>
    <div class="widget">
        <h2 class="title"></h2>
        <div id="query-info-container"></div>
    </div>
</body>
</html>

Passaggio 5: Manifesto dell'estensione Aggiornamenti

In questo passaggio si aggiorna il manifesto dell'estensione per includere una voce per il secondo widget. Aggiungere un nuovo contributo alla matrice nella contributions proprietà e aggiungere il nuovo file hello-world2.html alla matrice nella proprietà files. È necessaria un'altra immagine di anteprima per il secondo widget. preview2.png Denominarlo e inserirlo nella img cartella .

 {
     ...,
     "contributions":[
         ...,
        {
             "id": "HelloWorldWidget2",
             "type": "ms.vss-dashboards-web.widget",
             "targets": [
                 "ms.vss-dashboards-web.widget-catalog"
             ],
             "properties": {
                 "name": "Hello World Widget 2 (with API)",
                 "description": "My second widget",
                 "previewImageUrl": "img/preview2.png",                            
                 "uri": "hello-world2.html",
                 "supportedSizes": [
                      {
                             "rowSpan": 1,
                             "columnSpan": 2
                         }
                     ],
                 "supportedScopes": ["project_team"]
             }
         }

     ],
     "files": [
         {
             "path": "hello-world.html", "addressable": true
         },
         {
             "path": "hello-world2.html", "addressable": true
         },      
         {
             "path": "sdk/scripts", "addressable": true
         },
         {
             "path": "img", "addressable": true
         }
     ],
     "scopes":[
         "vso.work"
     ]
 }

Passaggio 6: Creare un pacchetto, pubblicare e condividere

Creare un pacchetto, pubblicare e condividere l'estensione. Se l'estensione è già stata pubblicata, è possibile creare nuovamente il pacchetto dell'estensione e aggiornarla direttamente in Marketplace.

Passaggio 7: Aggiungere widget dal catalogo

Passare ora al dashboard del team all'indirizzo https:\//dev.azure.com/{yourOrganization}/{yourProject}. Se questa pagina è già aperta, aggiornarla. Passare il puntatore del mouse sul pulsante Modifica in basso a destra e selezionare il pulsante Aggiungi. Viene aperto il catalogo dei widget in cui si trova il widget installato. Scegliere il widget e selezionare il pulsante "Aggiungi" per aggiungerlo al dashboard.

Parte 3: Hello World con configurazione

Nella parte 2 di questa guida è stato illustrato come creare un widget che mostra le informazioni sulle query per una query hardcoded. In questa parte viene aggiunta la possibilità di configurare la query da usare anziché quella hardcoded. Quando si usa la modalità di configurazione, l'utente può visualizzare un'anteprima in tempo reale del widget in base alle modifiche. Queste modifiche vengono salvate nel widget nel dashboard quando l'utente seleziona Salva.

Overview dashboard live preview of the widget based on changes.

Passaggio 1: HTML

Le implementazioni di widget e configurazioni dei widget sono molto simili. Entrambi vengono implementati nel framework di estensione come contributi. Entrambi usano lo stesso file SDK, VSS.SDK.min.js. Entrambi sono basati su HTML, JavaScript e CSS.

Copiare il file html-world2.html dall'esempio precedente e rinominare la copia in hello-world3.html. Aggiungere un altro file HTML denominato configuration.html. La cartella è ora simile all'esempio seguente:

|--- README.md
|--- sdk    
    |--- node_modules           
    |--- scripts
        |--- VSS.SDK.min.js       
|--- img                        
    |--- logo.png                           
|--- scripts          
|--- configuration.html                          
|--- hello-world.html               // html page to be used for your widget  
|--- hello-world2.html              // renamed copy of hello-world.html
|--- hello-world3.html              // renamed copy of hello-world2.html
|--- vss-extension.json             // extension's manifest

Aggiungere il codice HTML seguente in 'configuration.html'. In pratica, si aggiunge il riferimento obbligatorio al servizio Copia Shadow del servizio Copia Shadow del report. File SDK.min.js' e un elemento 'select' per l'elenco a discesa per selezionare una query da un elenco preimpostato.
    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
        <head>                          
            <script src="sdk/scripts/VSS.SDK.min.js"></script>              
        </head>
        <body>
            <div class="container">
                <fieldset>
                    <label class="label">Query: </label>
                    <select id="query-path-dropdown" style="margin-top:10px">
                        <option value="" selected disabled hidden>Please select a query</option>
                        <option value="Shared Queries/Feedback">Shared Queries/Feedback</option>
                        <option value="Shared Queries/My Bugs">Shared Queries/My Bugs</option>
                        <option value="Shared Queries/My Tasks">Shared Queries/My Tasks</option>                        
                    </select>
                </fieldset>             
            </div>
        </body>
    </html>

Passaggio 2: JavaScript - Configurazione

Usare JavaScript per eseguire il rendering del contenuto nella configurazione del widget esattamente come è stato fatto per il widget nel passaggio 3 della parte 1 in questa guida. Questo codice JavaScript esegue il rendering del contenuto, inizializza VSS SDK, esegue il mapping del codice per la configurazione del widget al nome della configurazione e passa le impostazioni di configurazione al framework. In questo caso, di seguito è riportato il codice che carica la configurazione del widget. Aprire il file configuration.html e l'elemento seguente <script> in <head>.

    <script type="text/javascript">
        VSS.init({                        
            explicitNotifyLoaded: true,
            usePlatformStyles: true
        });

        VSS.require("TFS/Dashboards/WidgetHelpers", function (WidgetHelpers) {
            VSS.register("HelloWorldWidget.Configuration", function () {   
                var $queryDropdown = $("#query-path-dropdown"); 

                return {
                    load: function (widgetSettings, widgetConfigurationContext) {
                        var settings = JSON.parse(widgetSettings.customSettings.data);
                        if (settings && settings.queryPath) {
                             $queryDropdown.val(settings.queryPath);
                         }

                        return WidgetHelpers.WidgetStatusHelper.Success();
                    },
                    onSave: function() {
                        var customSettings = {
                            data: JSON.stringify({
                                    queryPath: $queryDropdown.val()
                                })
                        };
                        return WidgetHelpers.WidgetConfigurationSave.Valid(customSettings); 
                    }
                }
            });
            VSS.notifyLoadSucceeded();
        });
    </script>

VSS.init, VSS.requiree VSS.register svolgono lo stesso ruolo svolto per il widget, come descritto nella parte 1. L'unica differenza è che per le configurazioni dei widget, la funzione passata a VSS.register deve restituire un oggetto che soddisfa il IWidgetConfiguration contratto.

La load proprietà del IWidgetConfiguration contratto deve avere una funzione come valore. Questa funzione include il set di passaggi per eseguire il rendering della configurazione del widget. In questo caso, è aggiornare il valore selezionato dell'elemento a discesa con le impostazioni esistenti, se presenti. Questa funzione viene chiamata quando il framework crea un'istanza del widget configuration

La onSave proprietà del IWidgetConfiguration contratto deve avere una funzione come valore. Questa funzione viene chiamata dal framework quando l'utente seleziona Salva nel riquadro di configurazione. Se l'input dell'utente è pronto per il salvataggio, serializzarlo in una stringa, formare l'oggetto custom settings e usare WidgetConfigurationSave.Valid() per salvare l'input dell'utente.

In questa guida viene usato JSON per serializzare l'input dell'utente in una stringa. È possibile scegliere qualsiasi altro modo per serializzare l'input dell'utente nella stringa. È accessibile al widget tramite la proprietà personalizzata Impostazioni dell'oggetto WidgetSettings . Il widget deve deserializzare questa operazione, descritta nel passaggio 4.

Passaggio 3: JavaScript - Abilitare l'anteprima in tempo reale

Per abilitare l'aggiornamento dell'anteprima in tempo reale quando l'utente seleziona una query dall'elenco a discesa, allega un gestore eventi di modifica al pulsante. Questo gestore notifica al framework che la configurazione è stata modificata. Passa anche l'oggetto customSettings da usare per l'aggiornamento dell'anteprima. Per notificare al framework, è necessario chiamare il notify metodo sull'oggetto widgetConfigurationContext . Accetta due parametri, il nome dell'evento, che in questo caso è WidgetHelpers.WidgetEvent.ConfigurationChangee un EventArgs oggetto per l'evento, creato da customSettings con l'aiuto del metodo helper WidgetEvent.Args .

Aggiungere quanto segue nella funzione assegnata alla load proprietà .

 $queryDropdown.on("change", function () {
     var customSettings = {
        data: JSON.stringify({
                queryPath: $queryDropdown.val()
            })
     };
     var eventName = WidgetHelpers.WidgetEvent.ConfigurationChange;
     var eventArgs = WidgetHelpers.WidgetEvent.Args(customSettings);
     widgetConfigurationContext.notify(eventName, eventArgs);
 });

È necessario notificare al framework la modifica della configurazione almeno una volta in modo che il pulsante "Salva" possa essere abilitato.

Alla fine, configuration.html l'aspetto è simile al seguente:

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
        <head>                          
            <script src="sdk/scripts/VSS.SDK.min.js"></script>      
            <script type="text/javascript">
                VSS.init({                        
                    explicitNotifyLoaded: true,
                    usePlatformStyles: true
                });

                VSS.require("TFS/Dashboards/WidgetHelpers", function (WidgetHelpers) {
                    VSS.register("HelloWorldWidget.Configuration", function () {   
                        var $queryDropdown = $("#query-path-dropdown");

                        return {
                            load: function (widgetSettings, widgetConfigurationContext) {
                                var settings = JSON.parse(widgetSettings.customSettings.data);
                                if (settings && settings.queryPath) {
                                     $queryDropdown.val(settings.queryPath);
                                 }

                                 $queryDropdown.on("change", function () {
                                     var customSettings = {data: JSON.stringify({queryPath: $queryDropdown.val()})};
                                     var eventName = WidgetHelpers.WidgetEvent.ConfigurationChange;
                                     var eventArgs = WidgetHelpers.WidgetEvent.Args(customSettings);
                                     widgetConfigurationContext.notify(eventName, eventArgs);
                                 });

                                return WidgetHelpers.WidgetStatusHelper.Success();
                            },
                            onSave: function() {
                                var customSettings = {data: JSON.stringify({queryPath: $queryDropdown.val()})};
                                return WidgetHelpers.WidgetConfigurationSave.Valid(customSettings); 
                            }
                        }
                    });
                    VSS.notifyLoadSucceeded();
                });
            </script>       
        </head>
        <body>
            <div class="container">
                <fieldset>
                    <label class="label">Query: </label>
                    <select id="query-path-dropdown" style="margin-top:10px">
                        <option value="" selected disabled hidden>Please select a query</option>
                        <option value="Shared Queries/Feedback">Shared Queries/Feedback</option>
                        <option value="Shared Queries/My Bugs">Shared Queries/My Bugs</option>
                        <option value="Shared Queries/My Tasks">Shared Queries/My Tasks</option>                        
                    </select>
                </fieldset>     
            </div>
        </body>
    </html>

Passaggio 4: JavaScript - Implementare il ricaricamento nel widget

È stata configurata la configurazione del widget per archiviare il percorso di query selezionato dall'utente. È ora necessario aggiornare il codice nel widget per usare questa configurazione archiviata anziché il hardcoded Shared Queries/Feedback dell'esempio precedente.

Aprire il file hello-world3.html e aggiornare il nome del widget da HelloWorldWidget2 a HelloWorldWidget3 nella riga in cui si chiama VSS.register. Ciò consente al framework di identificare in modo univoco il widget all'interno dell'estensione.

La funzione mappata a HelloWorldWidget3 tramite VSS.register restituisce attualmente un oggetto che soddisfa il IWidget contratto. Poiché il widget richiede ora la configurazione, questa funzione deve essere aggiornata per restituire un oggetto che soddisfa il IConfigurableWidget contratto. A tale scopo, aggiornare l'istruzione return per includere una proprietà denominata ricaricamento come indicato di seguito. Il valore di questa proprietà è una funzione che chiama il getQueryInfo metodo una volta. Questo metodo di ricaricamento viene chiamato dal framework ogni volta che l'input dell'utente cambia per visualizzare l'anteprima in tempo reale. Questa operazione viene chiamata anche quando viene salvata la configurazione.

return {
    load: function (widgetSettings) {
        // Set your title
        var $title = $('h2.title');
        $title.text('Hello World');

        return getQueryInfo(widgetSettings);
    },
    reload: function (widgetSettings) {
        return getQueryInfo(widgetSettings);
    }
}

Il percorso di query hardcoded in 'getQueryInfo' deve essere sostituito con il percorso di query configurato, che può essere estratto dal parametro 'widget Impostazioni' passato al metodo . Aggiungere quanto segue all'inizio del metodo 'getQueryInfo' e sostituire il percorso di query hardcoded con 'settings.queryPath'.
var settings = JSON.parse(widgetSettings.customSettings.data);
if (!settings || !settings.queryPath) {
    var $container = $('#query-info-container');
    $container.empty();
    $container.text("Sorry nothing to show, please configure a query path.");

    return WidgetHelpers.WidgetStatusHelper.Success();
}

A questo punto, il widget è pronto per il rendering con le impostazioni configurate.

Sia le load proprietà che le reload proprietà hanno una funzione simile. Questo è il caso per i widget più semplici. Per i widget complessi, ci sarebbero alcune operazioni che si desidera eseguire una sola volta, indipendentemente dal numero di modifiche alla configurazione. In alternativa, potrebbero esserci alcune operazioni pesanti che non devono essere eseguite più di una volta. Tali operazioni fanno parte della funzione corrispondente alla load proprietà e non alla reload proprietà .

Passaggio 5: Manifesto dell'estensione Aggiornamenti

Aprire il vss-extension.json file per includere due nuove voci nella matrice nella contributions proprietà . Uno per il HelloWorldWidget3 widget e l'altro per la configurazione. È necessaria un'altra immagine di anteprima per il terzo widget. preview3.png Denominarlo e inserirlo nella img cartella . Aggiornare la matrice nella files proprietà per includere i due nuovi file HTML aggiunti in questo esempio.

{
    ...
    "contributions": [
        ... , 
        {
             "id": "HelloWorldWidget3",
             "type": "ms.vss-dashboards-web.widget",
             "targets": [
                 "ms.vss-dashboards-web.widget-catalog",
                 "fabrikam.vsts-extensions-myExtensions.HelloWorldWidget.Configuration"
             ],
             "properties": {
                 "name": "Hello World Widget 3 (with config)",
                 "description": "My third widget",
                 "previewImageUrl": "img/preview3.png",                       
                 "uri": "hello-world3.html",
                 "supportedSizes": [
                      {
                             "rowSpan": 1,
                             "columnSpan": 2
                         }
                     ],
                 "supportedScopes": ["project_team"]
             }
         },
         {
             "id": "HelloWorldWidget.Configuration",
             "type": "ms.vss-dashboards-web.widget-configuration",
             "targets": [ "ms.vss-dashboards-web.widget-configuration" ],
             "properties": {
                 "name": "HelloWorldWidget Configuration",
                 "description": "Configures HelloWorldWidget",
                 "uri": "configuration.html"
             }
         }
    ],
    "files": [
            {
                "path": "hello-world.html", "addressable": true
            },
             {
                "path": "hello-world2.html", "addressable": true
            },
            {
                "path": "hello-world3.html", "addressable": true
            },
            {
                "path": "configuration.html", "addressable": true
            },
            {
                "path": "sdk/scripts", "addressable": true
            },
            {
                "path": "img", "addressable": true
            }
        ],
        ...     
}

Si noti che il contributo per la configurazione del widget segue un modello leggermente diverso rispetto al widget stesso. Una voce di contributo per la configurazione del widget include:
  • ID per identificare il contributo. Deve essere univoco all'interno di un'estensione.
  • Tipo di contributo. Per tutte le configurazioni dei widget, questo dovrebbe essere ms.vss-dashboards-web.widget-configuration
  • Matrice di destinazioni a cui contribuisce il contributo. Per tutte le configurazioni dei widget, è presente una singola voce: ms.vss-dashboards-web.widget-configuration.
  • Proprietà che contengono un set di proprietà che include nome, descrizione e URI del file HTML utilizzato per la configurazione.

Per supportare la configurazione, è necessario modificare anche il contributo del widget. La matrice di destinazioni per il widget deve essere aggiornata per includere l'ID per la configurazione nel formato <publisher>.<>id for the extension.<id for the configuration contribution> In questo caso è .fabrikam.vsts-extensions-myExtensions.HelloWorldWidget.Configuration

Avviso

Se la voce di contributo per il widget configurabile non è destinata alla configurazione usando il nome corretto dell'editore e dell'estensione come descritto in precedenza, il pulsante configura non viene visualizzato per il widget.

Alla fine di questa parte, il file manifesto deve contenere tre widget e una configurazione. È possibile ottenere il manifesto completo dall'esempio qui.

Passaggio 6: Creare un pacchetto, pubblicare e condividere

Se l'estensione non è ancora stata pubblicata, leggere questa sezione per creare pacchetti, pubblicare e condividere l'estensione. Se l'estensione è già stata pubblicata prima di questo punto, è possibile creare nuovamente il pacchetto dell'estensione e aggiornarla direttamente nel Marketplace.

Passaggio 7: Aggiungere widget dal catalogo

Passare ora al dashboard del team all'indirizzo https://dev.azure.com/{yourOrganization}/{yourProject}. Se questa pagina è già aperta, aggiornarla. Passare il puntatore del mouse sul pulsante Modifica in basso a destra e selezionare il pulsante Aggiungi. Verrà aperto il catalogo dei widget in cui si trova il widget installato. Scegliere il widget e selezionare il pulsante "Aggiungi" per aggiungerlo al dashboard.

Verrà visualizzato un messaggio che chiede di configurare il widget.

Overview dashboard with a sample widget from the catalog.

Esistono due modi per configurare i widget. Uno consiste nel passare il puntatore del mouse sul widget, selezionare i puntini di sospensione visualizzati nell'angolo in alto a destra e quindi selezionare Configura. L'altro consiste nel selezionare il pulsante Modifica in basso a destra del dashboard e quindi selezionare il pulsante Configura visualizzato nell'angolo in alto a destra del widget. Apre l'esperienza di configurazione sul lato destro e un'anteprima del widget al centro. Procedere e scegliere una query dall'elenco a discesa. L'anteprima live mostra i risultati aggiornati. Selezionare "Salva" e il widget visualizza i risultati aggiornati.

Passaggio 8: Configurare altro (facoltativo)

È possibile aggiungere tutti gli elementi del modulo HTML necessari in configuration.html per una configurazione aggiuntiva. Sono disponibili due funzionalità configurabili: il nome del widget e le dimensioni del widget.

Per impostazione predefinita, il nome specificato per il widget nel manifesto dell'estensione viene archiviato come nome del widget per ogni istanza del widget che viene aggiunto a un dashboard. È possibile consentire agli utenti di configurare questa impostazione, in modo che possano aggiungere qualsiasi nome che desiderano all'istanza del widget. Per consentire tale configurazione, aggiungere isNameConfigurable:true nella sezione delle proprietà per il widget nel manifesto dell'estensione.

Se si specificano più voci per il supportedSizes widget nella matrice nel manifesto dell'estensione, gli utenti possono configurare anche le dimensioni del widget.

Il manifesto dell'estensione per il terzo esempio in questa guida sarà simile al seguente se si abilita il nome del widget e la configurazione delle dimensioni:

{
    ...
    "contributions": [
        ... , 
        {
             "id": "HelloWorldWidget3",
             "type": "ms.vss-dashboards-web.widget",
             "targets": [
                 "ms.vss-dashboards-web.widget-catalog",  "fabrikam.vsts-extensions-myExtensions.HelloWorldWidget.Configuration"
             ],
             "properties": {
                 "name": "Hello World Widget 3 (with config)",
                 "description": "My third widget",
                 "previewImageUrl": "img/preview3.png",                       
                 "uri": "hello-world3.html",
                 "isNameConfigurable": true,
                 "supportedSizes": [
                    {
                        "rowSpan": 1,
                        "columnSpan": 2
                    },
                    {
                        "rowSpan": 2,
                        "columnSpan": 2
                    }
                 ],
                 "supportedScopes": ["project_team"]
             }
         },
         ...
}

Con la modifica precedente, ripacchetto e aggiornamento dell'estensione. Aggiornare il dashboard con questo widget (Hello World Widget 3 (con configurazione).Refresh the dashboard that has this widget (Hello World Widget 3 (with config)). Aprire la modalità di configurazione per il widget, ora dovrebbe essere possibile visualizzare l'opzione per modificare il nome e le dimensioni del widget.

Widget where name and size can be configured

Vai avanti e scegli una dimensione diversa dall'elenco a discesa. Viene visualizzata l'anteprima live ridimensionata. Salvare la modifica e anche il widget nel dashboard viene ridimensionato.

Avviso

Se si rimuove una dimensione già supportata, il widget non viene caricato correttamente. Microsoft sta lavorando a una correzione per una versione futura.

La modifica del nome del widget non comporta alcuna modifica visibile nel widget. Questo perché i widget di esempio non visualizzano il nome del widget da nessuna parte. Modificare il codice di esempio per visualizzare il nome del widget anziché il testo hardcoded "Hello World".

A tale scopo, sostituire il testo hardcoded "Hello World" con widgetSettings.name nella riga in cui impostiamo il testo dell'elemento h2 . In questo modo si garantisce che il nome del widget venga visualizzato ogni volta che il widget viene caricato all'aggiornamento della pagina. Poiché si vuole che l'anteprima in tempo reale venga aggiornata ogni volta che la configurazione cambia, è necessario aggiungere anche lo reload stesso codice nella parte del codice. L'istruzione return finale in hello-world3.html è la seguente:

return {
    load: function (widgetSettings) {
        // Set your title
        var $title = $('h2.title');
        $title.text(widgetSettings.name);

        return getQueryInfo(widgetSettings);
    },
    reload: function (widgetSettings) {
        // Set your title
        var $title = $('h2.title');
        $title.text(widgetSettings.name);

        return getQueryInfo(widgetSettings);
    }
}

Ripacchetto e aggiornare di nuovo l'estensione. Aggiornare il dashboard con questo widget. Tutte le modifiche apportate al nome del widget, nella modalità di configurazione, aggiornano ora il titolo del widget.