Condividi tramite


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 sulla precedente. Iniziare con un widget semplice e terminare con un widget completo.

Suggerimento

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

Prerequisiti

Requisito Descrizione
Conoscenza della programmazione Conoscenza javaScript, HTML e CSS per lo sviluppo di widget
Organizzazione DevOps di Azure Creare un'organizzazione
Editor di testo Per le esercitazioni viene usato Visual Studio Code
Node.js Versione più recente di Node.js
Interfaccia della riga di comando multipiattaforma tfx-cli per impacchettare estensioni
Installare con: npm i -g tfx-cli
Directory del progetto Home directory con questa struttura dopo aver completato l'esercitazione:

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

Panoramica delle esercitazioni

Questa esercitazione illustra lo sviluppo di widget tramite tre esempi progressivi:

Parte Concentrazione (or Messa a fuoco) Cosa imparerai
Parte 1: Hello World Creazione di widget di base Creare un widget che visualizza il testo
Parte 2: Integrazione dell'API REST Chiamate API di Azure DevOps Aggiungere la funzionalità dell'API REST per recuperare e visualizzare i dati
Parte 3: Configurazione del widget Personalizzazione utente Implementare le opzioni di configurazione per il widget

Suggerimento

Ignorare l'esercitazione: Scaricare l'estensione di esempio completa, passare alla widgets cartella e passare al passaggio 6 per pubblicare tre widget di esempio pronti per l'uso.

Prima di iniziare, esaminare gli stili dei widget di base e le linee guida strutturali fornite.

Parte 1: Hello World

Creare un widget di base che visualizza "Hello World" usando JavaScript. Questa base illustra i concetti di base relativi allo sviluppo di widget.

Screenshot del dashboard Panoramica con un widget di esempio.

Passaggio 1: Installare l'SDK client

VSS SDK consente al widget di comunicare con Azure DevOps. Installarlo con npm:

npm install vss-web-extension-sdk

Copiare il VSS.SDK.min.js file da vss-web-extension-sdk/lib nella home/sdk/scripts cartella.

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

Passaggio 2: Creare la struttura HTML

Crea hello-world.html nella directory del progetto. Questo file fornisce il layout e i riferimenti del widget agli script necessari.

<!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>

I widget vengono eseguiti in iframe, quindi la maggior parte degli elementi head HTML tranne <script> e <link> vengono ignorati dal framework.

Passaggio 3: Aggiungere il widget JavaScript

Per implementare la funzionalità del widget, aggiungere questo script alla <head> sezione del file HTML:

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

    VSS.require(["AzureDevOps/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>

Componenti JavaScript principali

Funzione Scopo
VSS.init() Inizializza la comunicazione tra widget e Azure DevOps
VSS.require() Carica le librerie SDK necessarie e gli helper widget
VSS.register() Registra il widget con un identificatore univoco
WidgetHelpers.IncludeWidgetStyles() Applica lo stile predefinito di Azure DevOps
VSS.notifyLoadSucceeded() Notifica al framework che il caricamento è stato completato correttamente

Importante

Il nome del widget in VSS.register() deve corrispondere a quello nel manifesto dell'estensione id (Passaggio 5).

Passaggio 4: Aggiungere immagini di estensione

Creare le immagini necessarie per l'estensione:

  • Logo dell'estensione: immagine pixel 98x98 denominata logo.png nella img cartella
  • Icona del catalogo widget: immagine 98×98 pixel nella cartella denominata CatalogIcon.pngimg
  • Anteprima widget: immagine di 330x160 pixel denominata preview.png nella img cartella

Queste immagini vengono visualizzate nel catalogo marketplace e widget quando gli utenti esplorano le estensioni disponibili.

Passaggio 5: Creare il manifesto dell'estensione

Crea vss-extension.json nella directory radice del progetto. Questo file definisce i metadati e i contributi dell'estensione:

{
    "manifestVersion": 1,
    "id": "azure-devops-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
        }
    ]
}

Importante

Sostituire "publisher": "fabrikam" con il nome effettivo dell'editore. Informazioni su come creare un editore.

Proprietà essenziali del manifesto

Sezione Scopo
Informazioni di base Nome dell'estensione, versione, descrizione e autore
icone Percorsi delle risorse visive dell'estensione
Contributi Definizioni di widget, tra cui ID, tipo e proprietà
File Tutti i file da includere nel pacchetto di estensione

Per la documentazione completa del manifesto, vedere Informazioni di riferimento sul manifesto dell'estensione.

Passaggio 6: Creare un pacchetto e pubblicare l'estensione

Creare il pacchetto dell'estensione e pubblicarlo in Visual Studio Marketplace.

Installare lo strumento di creazione pacchetti

npm i -g tfx-cli

Creare il pacchetto di estensione

Dalla directory del progetto, esegui:

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

Questa azione crea un .vsix file che contiene l'estensione in pacchetto.

Configurare un server di pubblicazione

  1. Passare al portale di pubblicazione di Visual Studio Marketplace.
  2. Accedi e crea un publisher se non ne hai uno.
  3. Scegliere un identificatore di autore univoco (usato nel file manifesto).
  4. Aggiorna vss-extension.json per usare il tuo nome del publisher anziché "fabrikam".

Caricare l'estensione

  1. Nel portale di pubblicazione selezionare Carica nuova estensione.
  2. Scegliere il .vsix file e caricarlo.
  3. Condividere l'estensione con l'organizzazione di Azure DevOps.

In alternativa, usare la riga di comando:

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

Suggerimento

Usare --rev-version per incrementare automaticamente il numero di versione durante l'aggiornamento di un'estensione esistente.

Passaggio 7: Installare e testare il widget

Per testare, aggiungere il widget a un dashboard:

  1. Passare al progetto Azure DevOps: https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Passare a Panoramica>Dashboard.
  3. Seleziona Aggiungi un widget.
  4. Trovare il widget nel catalogo e selezionare Aggiungi.

Il widget "Hello World" viene visualizzato nel dashboard, visualizzando il testo configurato.

Passaggio successivo: Passare alla parte 2 per informazioni su come integrare le API REST di Azure DevOps nel widget.

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

Estendere il widget per interagire con i dati di Azure DevOps usando le API REST. In questo esempio viene illustrato come recuperare le informazioni sulle query e visualizzarle in modo dinamico nel widget.

In questa parte, usare l'API REST per il monitoraggio degli elementi di lavoro per recuperare informazioni su una query esistente e visualizzare i dettagli della query sotto il testo "Hello World".

Screenshot della dashboard di panoramica con un widget di esempio che utilizza l'API REST per WorkItemTracking.

Passaggio 1: Creare il file HTML avanzato

Creare un nuovo file widget basato sull'esempio precedente. Copiare hello-world.html e rinominarlo in hello-world2.html. La struttura del progetto include ora:

|--- README.md
|--- node_modules
|--- sdk/
    |--- scripts/
        |--- VSS.SDK.min.js
|--- img/
    |--- logo.png
|--- scripts/
|--- hello-world.html               // Part 1 widget
|--- hello-world2.html              // Part 2 widget (new)
|--- vss-extension.json             // Extension manifest

Aggiornare la struttura HTML del widget

Apportare queste modifiche a hello-world2.html:

  1. Aggiungere un contenitore per i dati di query: includere un nuovo <div> elemento per visualizzare le informazioni sulle query.
  2. Aggiornare l'identificatore del widget: modificare il nome del widget da HelloWorldWidget a per HelloWorldWidget2 l'identificazione univoca.
<!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(["AzureDevOps/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: Configurare le autorizzazioni di accesso all'API

Prima di effettuare chiamate API REST, configurare le autorizzazioni necessarie nel manifesto dell'estensione.

Aggiungere l'ambito di lavoro

L'ambito vso.work concede l'accesso in sola lettura agli elementi di lavoro e alle query. Aggiungi questo ambito a vss-extension.json:

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

Esempio di manifesto completo

Per un manifesto completo con altre proprietà, strutturarlo come segue:

{
    "name": "example-widget",
    "publisher": "example-publisher", 
    "version": "1.0.0",
    "scopes": [
        "vso.work"
    ]
}

Importante

Limitazioni dell'ambito: l'aggiunta o la modifica degli ambiti dopo la pubblicazione non è supportata. Se l'estensione è già stata pubblicata, è necessario rimuoverla prima dal Marketplace. Passare al portale di pubblicazione di Visual Studio Marketplace, trovare l'estensione e selezionare Rimuovi.

Passaggio 3: Implementare l'integrazione dell'API REST

Azure DevOps fornisce librerie client REST JavaScript tramite l'SDK. Queste librerie eseguono il wrapping delle chiamate AJAX e mappano le risposte api agli oggetti utilizzabili.

Aggiornare il widget JavaScript

Sostituire la chiamata VSS.require nel tuo hello-world2.html per includere il client REST di tracciamento degli elementi di lavoro.

VSS.require(["AzureDevOps/Dashboards/WidgetHelpers", "AzureDevOps/WorkItemTracking/RestClient"], 
    function (WidgetHelpers, WorkItemTrackingRestClient) {
        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 WorkItemTrackingRestClient.getClient().getQuery(projectId, "Shared Queries/Feedback")
                    .then(function (query) {
                        // Process query data (implemented in Step 4)

                        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();
    });

Dettagli chiave dell'implementazione

Componente Scopo
WorkItemTrackingRestClient.getClient() Ottiene un'istanza del client REST di rilevamento degli elementi di lavoro
getQuery() Recupera le informazioni sulla query incapsulata in una promise
WidgetStatusHelper.Failure() Fornisce una gestione coerente degli errori per i guasti del widget
projectId Contesto di progetto corrente necessario per le chiamate API

Suggerimento

Percorsi di query personalizzati: se non si ha una query "Feedback" in "Query condivise", sostituire "Shared Queries/Feedback" con il percorso di qualsiasi query presente nel progetto.

Passaggio 4: Visualizzare i dati di risposta dell'API

Esegui il rendering delle informazioni della query nel tuo widget elaborando la risposta dell'API REST.

Aggiungere il rendering dei dati delle query

Sostituire il // Process query data commento con questa implementazione:

// Create a list with query details                                
var $list = $('<ul>');                                
$list.append($('<li>').text("Query ID: " + query.id));
$list.append($('<li>').text("Query Name: " + query.name));
$list.append($('<li>').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);

Il getQuery() metodo restituisce un Contracts.QueryHierarchyItem oggetto con proprietà per i metadati della query. In questo esempio vengono visualizzate tre informazioni chiave sotto il testo "Hello World".

Esempio di lavoro completo

Il file finale hello-world2.html sarà simile al 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(["AzureDevOps/Dashboards/WidgetHelpers", "AzureDevOps/WorkItemTracking/RestClient"], 
            function (WidgetHelpers, WorkItemTrackingRestClient) {
                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 WorkItemTrackingRestClient.getClient().getQuery(projectId, "Shared Queries/Feedback")
                            .then(function (query) {
                                // Create a list with query details                                
                                var $list = $('<ul>');
                                $list.append($('<li>').text("Query ID: " + query.id));
                                $list.append($('<li>').text("Query Name: " + query.name));
                                $list.append($('<li>').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: Aggiornare il manifesto dell'estensione

Per renderlo disponibile nel catalogo dei widget, aggiungere il nuovo widget al manifesto dell'estensione.

Aggiungere il secondo contributo del widget

Aggiornare vss-extension.json per includere il widget abilitato per l'API REST. Aggiungere questo contributo alla contributions matrice:

{
    "contributions": [
        // ...existing HelloWorldWidget contribution...,
        {
            "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"
    ]
}

Suggerimento

Immagine di anteprima: creare un'immagine preview2.png (330x160 pixel) e inserirla nella img cartella per mostrare agli utenti l'aspetto del widget nel catalogo.

Passaggio 6: Creare un pacchetto, pubblicare e condividere

Creare un pacchetto, pubblicare e condividere l'estensione. Se l'estensione è già stata pubblicata, è possibile riassemblarla e aggiornarla direttamente nel Marketplace.

Passaggio 7: Testare il widget dell'API REST

Per visualizzare l'integrazione dell'API REST in azione, aggiungere il nuovo widget al dashboard:

  1. Passare al progetto Azure DevOps: https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Selezionare Panoramica>Dashboard.
  3. Seleziona Aggiungi un widget.
  4. Trovare "Hello World Widget 2 (con API)" e selezionare Aggiungi.

Il widget avanzato visualizza sia il testo "Hello World" che le informazioni sulle query in tempo reale del progetto Azure DevOps.

Passaggi successivi: passare alla parte 3 per aggiungere opzioni di configurazione che consentono agli utenti di personalizzare la query da visualizzare.

Parte 3: Configurare Hello World

Compilare la parte 2 aggiungendo funzionalità di configurazione utente al widget. Invece di impostare come hardcoded il percorso della query, creare un'interfaccia di configurazione che consente agli utenti di selezionare la query da visualizzare, con funzionalità di anteprima in tempo reale.

Questa parte illustra come creare widget configurabili che gli utenti possono personalizzare in base alle proprie esigenze specifiche fornendo feedback in tempo reale durante la configurazione.

Screenshot dell'anteprima in tempo reale del dashboard panoramica del widget in base alle modifiche.

Passaggio 1: Creare file di configurazione

Le configurazioni dei widget condividono molte analogie con i widget stessi, entrambi usano lo stesso SDK, la struttura HTML e i modelli JavaScript, ma servono scopi diversi all'interno del framework di estensione.

Configurare la struttura del progetto

Per supportare la configurazione del widget, creare due nuovi file:

  1. Copiare hello-world2.html e rinominarlo in hello-world3.html, il widget configurabile.
  2. Creare un nuovo file denominato configuration.html, che gestisce l'interfaccia di configurazione.

La struttura del progetto include ora:

|--- README.md
|--- sdk/    
    |--- node_modules           
    |--- scripts/
        |--- VSS.SDK.min.js       
|--- img/                        
    |--- logo.png                           
|--- scripts/          
|--- configuration.html              // New: Configuration interface
|--- hello-world.html               // Part 1: Basic widget  
|--- hello-world2.html              // Part 2: REST API widget
|--- hello-world3.html              // Part 3: Configurable widget (new)
|--- vss-extension.json             // Extension manifest

Creare l'interfaccia di configurazione

Aggiungere questa struttura HTML a configuration.html, che crea un selettore a discesa per la scelta delle query:

<!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: Implementare la configurazione javaScript

Configuration JavaScript segue lo stesso modello di inizializzazione dei widget, ma implementa il IWidgetConfiguration contratto anziché il contratto di base IWidget .

Aggiungere la logica di configurazione

Inserire questo script nella <head> sezione di configuration.html:

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

    VSS.require(["AzureDevOps/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>

Dettagli del contratto di configurazione

Il IWidgetConfiguration contratto richiede queste funzioni chiave:

Funzione Scopo Quando viene chiamato
load() Inizializzare l'interfaccia utente di configurazione con le impostazioni esistenti Quando si apre la finestra di dialogo di configurazione
onSave() Serializzare l'input dell'utente e convalidare le impostazioni Quando l'utente seleziona Salva

Suggerimento

Serializzazione dei dati: questo esempio usa JSON per serializzare le impostazioni. Il widget accede a queste impostazioni tramite widgetSettings.customSettings.data e deve deserializzarli di conseguenza.

Passaggio 3: Abilitare la funzionalità di anteprima in tempo reale

L'anteprima in tempo reale consente agli utenti di visualizzare immediatamente le modifiche dei widget quando modificano le impostazioni di configurazione, fornendo feedback istantaneo prima del salvataggio.

Implementare le notifiche delle modifiche

Per abilitare l'anteprima live, aggiungere questo gestore eventi all'interno della load funzione :

$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);
});

File di configurazione completo

Il tuo configuration.html finale dovrebbe apparire così:

<!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(["AzureDevOps/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>

Importante

Abilita pulsante Salva: il framework richiede almeno una notifica di una modifica nella configurazione per abilitare il pulsante Salva. Il gestore eventi di modifica garantisce che questa azione venga eseguita quando gli utenti selezionano un'opzione.

Passaggio 4: Rendere il widget configurabile

Trasforma il tuo widget della Parte 2 per usare i dati di configurazione anziché i valori predefiniti. Questo passaggio richiede l'implementazione del IConfigurableWidget contratto.

Aggiornare la registrazione del widget

In hello-world3.htmlapportare queste modifiche:

  1. Aggiornare l'ID del widget: passare da HelloWorldWidget2 a HelloWorldWidget3.
  2. Aggiungere la funzione di ricaricamento: implementare il IConfigurableWidget contratto.
return {
    load: function (widgetSettings) {
        // Set your title
        var $title = $('h2.title');
        $title.text('Hello World');

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

Gestire i dati di configurazione

Aggiornare la funzione getQueryInfo per usare le impostazioni di configurazione anziché i percorsi di query codificati fissi:

var settings = JSON.parse(widgetSettings.customSettings.data);
if (!settings || !settings.queryPath) {
    var $container = $('#query-info-container');
    $container.empty();
    $container.text("Please configure a query path to display data.");

    return WidgetHelpers.WidgetStatusHelper.Success();
}

Differenze del ciclo di vita dei widget

Funzione Scopo Linee guida per l'utilizzo
load() Rendering iniziale del widget e configurazione monouso Operazioni pesanti, inizializzazione delle risorse
reload() Aggiornare il widget con la nuova configurazione Aggiornamenti leggeri, aggiornamento dei dati

Suggerimento

Ottimizzazione delle prestazioni: usare load() per operazioni costose che devono essere eseguite una sola volta e reload() per aggiornamenti rapidi in caso di modifiche alla configurazione.

(Facoltativo) Aggiungere un lightbox per informazioni dettagliate

I widget del dashboard hanno spazio limitato, rendendo difficile visualizzare informazioni complete. Un lightbox offre una soluzione elegante mostrando dati dettagliati in una sovrimpressione modale senza uscire dal dashboard.

Perché usare un lightbox nei widget?

Beneficio Descrizione
Efficienza dello spazio Mantenere il widget compatto offrendo visualizzazioni dettagliate
Esperienza utente Gestire il contesto del dashboard durante la visualizzazione di altre informazioni
Divulgazione progressiva Visualizzare i dati di riepilogo nel widget, i dettagli su richiesta
progettazione reattiva Adattarsi alle diverse dimensioni dello schermo e alle configurazioni dei widget

Implementare elementi selezionabili

Aggiornare il rendering dei dati della query in modo da includere elementi selezionabili che attivano la lightbox:

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

// Add a clickable element to open detailed view
var $detailsLink = $('<button class="details-link">View Details</button>');
$detailsLink.on('click', function() {
    showQueryDetails(query);
});

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

Creare la funzionalità lightbox

Aggiungi questa implementazione lightbox al tuo widget JavaScript.

function showQueryDetails(query) {
    // Create lightbox overlay
    var $overlay = $('<div class="lightbox-overlay">');
    var $lightbox = $('<div class="lightbox-content">');
    
    // Add close button
    var $closeBtn = $('<button class="lightbox-close">&times;</button>');
    $closeBtn.on('click', function() {
        $overlay.remove();
    });
    
    // Create detailed content
    var $content = $('<div class="query-details">');
    $content.append($('<h3>').text(query.name || 'Query Details'));
    $content.append($('<p>').html('<strong>ID:</strong> ' + query.id));
    $content.append($('<p>').html('<strong>Path:</strong> ' + query.path));
    $content.append($('<p>').html('<strong>Created:</strong> ' + (query.createdDate ? new Date(query.createdDate).toLocaleDateString() : 'Unknown')));
    $content.append($('<p>').html('<strong>Modified:</strong> ' + (query.lastModifiedDate ? new Date(query.lastModifiedDate).toLocaleDateString() : 'Unknown')));
    $content.append($('<p>').html('<strong>Created By:</strong> ' + (query.createdBy ? query.createdBy.displayName : 'Unknown')));
    $content.append($('<p>').html('<strong>Modified By:</strong> ' + (query.lastModifiedBy ? query.lastModifiedBy.displayName : 'Unknown')));
    
    if (query.queryType) {
        $content.append($('<p>').html('<strong>Type:</strong> ' + query.queryType));
    }
    
    // Assemble lightbox
    $lightbox.append($closeBtn);
    $lightbox.append($content);
    $overlay.append($lightbox);
    
    // Add to document and show
    $('body').append($overlay);
    
    // Close on overlay click
    $overlay.on('click', function(e) {
        if (e.target === $overlay[0]) {
            $overlay.remove();
        }
    });
    
    // Close on Escape key
    $(document).on('keydown.lightbox', function(e) {
        if (e.keyCode === 27) { // Escape key
            $overlay.remove();
            $(document).off('keydown.lightbox');
        }
    });
}

Aggiungi lo stile del lightbox

Includi stili CSS per lightbox nella sezione HTML <head> del widget:

<style>
.query-summary {
    list-style: none;
    padding: 0;
    margin: 10px 0;
}

.query-summary li {
    padding: 2px 0;
    font-size: 12px;
}

.details-link {
    background: #0078d4;
    color: white;
    border: none;
    padding: 4px 8px;
    font-size: 11px;
    cursor: pointer;
    border-radius: 2px;
    margin-top: 8px;
}

.details-link:hover {
    background: #106ebe;
}

.lightbox-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.7);
    z-index: 10000;
    display: flex;
    align-items: center;
    justify-content: center;
}

.lightbox-content {
    background: white;
    border-radius: 4px;
    padding: 20px;
    max-width: 500px;
    max-height: 80vh;
    overflow-y: auto;
    position: relative;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}

.lightbox-close {
    position: absolute;
    top: 10px;
    right: 15px;
    background: none;
    border: none;
    font-size: 24px;
    cursor: pointer;
    color: #666;
    line-height: 1;
}

.lightbox-close:hover {
    color: #000;
}

.query-details h3 {
    margin-top: 0;
    color: #323130;
}

.query-details p {
    margin: 8px 0;
    font-size: 14px;
    line-height: 1.4;
}
</style>

Implementazione avanzata dei widget

Il widget avanzato completo con la funzionalità lightbox:

<!DOCTYPE html>
<html>
<head>    
    <script src="sdk/scripts/VSS.SDK.min.js"></script>
    <style>
        /* Lightbox styles from above */
        .query-summary {
            list-style: none;
            padding: 0;
            margin: 10px 0;
        }
        
        .query-summary li {
            padding: 2px 0;
            font-size: 12px;
        }
        
        .details-link {
            background: #0078d4;
            color: white;
            border: none;
            padding: 4px 8px;
            font-size: 11px;
            cursor: pointer;
            border-radius: 2px;
            margin-top: 8px;
        }
        
        .details-link:hover {
            background: #106ebe;
        }
        
        .lightbox-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.7);
            z-index: 10000;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        
        .lightbox-content {
            background: white;
            border-radius: 4px;
            padding: 20px;
            max-width: 500px;
            max-height: 80vh;
            overflow-y: auto;
            position: relative;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
        }
        
        .lightbox-close {
            position: absolute;
            top: 10px;
            right: 15px;
            background: none;
            border: none;
            font-size: 24px;
            cursor: pointer;
            color: #666;
            line-height: 1;
        }
        
        .lightbox-close:hover {
            color: #000;
        }
        
        .query-details h3 {
            margin-top: 0;
            color: #323130;
        }
        
        .query-details p {
            margin: 8px 0;
            font-size: 14px;
            line-height: 1.4;
        }
    </style>
    <script type="text/javascript">
        VSS.init({
            explicitNotifyLoaded: true,
            usePlatformStyles: true
        });

        VSS.require(["AzureDevOps/Dashboards/WidgetHelpers", "AzureDevOps/WorkItemTracking/RestClient"], 
            function (WidgetHelpers, WorkItemTrackingRestClient) {
                WidgetHelpers.IncludeWidgetStyles();
                
                function showQueryDetails(query) {
                    // Lightbox implementation from above
                }
                
                VSS.register("HelloWorldWidget2", function () {                
                    var projectId = VSS.getWebContext().project.id;

                    var getQueryInfo = function (widgetSettings) {
                        return WorkItemTrackingRestClient.getClient().getQuery(projectId, "Shared Queries/Feedback")
                            .then(function (query) {
                                // Enhanced display with lightbox trigger
                                var $list = $('<ul class="query-summary">');                                
                                $list.append($('<li>').text("Query ID: " + query.id));
                                $list.append($('<li>').text("Query Name: " + query.name));
                                $list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName : "<unknown>")));

                                var $detailsLink = $('<button class="details-link">View Details</button>');
                                $detailsLink.on('click', function() {
                                    showQueryDetails(query);
                                });

                                var $container = $('#query-info-container');
                                $container.empty();
                                $container.append($list);
                                $container.append($detailsLink);

                                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();
        });       
    </script>
</head>
<body>
    <div class="widget">
        <h2 class="title"></h2>
        <div id="query-info-container"></div>
    </div>
</body>
</html>

Considerazioni sull'accessibilità: assicurarsi che la lightbox sia accessibile dalla tastiera e includa etichette appropriate per i lettori di schermo. Testare con le funzionalità di accessibilità predefinite di Azure DevOps.

Importante

Prestazioni: i lightbox devono essere caricati rapidamente. Considerare il caricamento differito dei dati dettagliati solo quando si apre la lightbox, invece di recuperare tutto in precedenza.

Passaggio 5: Configurare il manifesto dell'estensione

Registrare sia il widget configurabile che l'interfaccia di configurazione nel manifesto dell'estensione.

Aggiungere widget e contributi di configurazione

Aggiornamento vss-extension.json per includere due nuovi contributi:

{
    "contributions": [
        // ...existing contributions..., 
        {
             "id": "HelloWorldWidget3",
             "type": "ms.vss-dashboards-web.widget",
             "targets": [
                 "ms.vss-dashboards-web.widget-catalog",
                 "fabrikam.azuredevops-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
        }
    ]
}

Requisiti per i contributi di configurazione

Proprietà Scopo Valore obbligatorio
type Identifica il contributo come configurazione del widget ms.vss-dashboards-web.widget-configuration
targets Posizione in cui viene visualizzata la configurazione ms.vss-dashboards-web.widget-configuration
uri Percorso del file HTML di configurazione Percorso del file di configurazione

Modello di destinazione del widget

Per i widget configurabili, la targets matrice deve includere un riferimento alla configurazione:

<publisher>.<extension-id>.<configuration-id>

Avviso

Visibilità del pulsante di configurazione: se il widget non ha come destinazione correttamente il contributo alla configurazione, il pulsante Configura non viene visualizzato. Verificare che i nomi del distributore e dell'estensione corrispondano esattamente al vostro manifesto.

Passaggio 6: Creare un pacchetto, pubblicare e condividere

Distribuire l'estensione avanzata con funzionalità di configurazione.

Se si tratta della prima pubblicazione, seguire il passaggio 6: Creare un pacchetto, pubblicare e condividere. Per le estensioni esistenti, creare un nuovo pacchetto e aggiornare direttamente nel Marketplace.

Passaggio 7: Testare il widget configurabile

Provare il flusso di lavoro di configurazione completo aggiungendo e configurando il widget.

Aggiungere il widget al dashboard

  1. Passare a https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Passare a Panoramica>Dashboard.
  3. Seleziona Aggiungi un widget.
  4. Trovare "Hello World Widget 3 (con config)" e selezionare Aggiungi.

Viene visualizzato un prompt di configurazione perché il widget richiede l'installazione:

Screenshot del dashboard Panoramica con un esempio di widget del catalogo.

Configurare il widget

Accedere alla configurazione tramite uno dei metodi seguenti:

  • Menu widget: passare il puntatore del mouse sul widget, selezionare i puntini di sospensione (⋯), quindi Configura
  • Modalità di modifica dashboard: selezionare Modifica nel dashboard, quindi il pulsante Configura nel widget

Il pannello di configurazione viene aperto con un'anteprima in tempo reale al centro. Selezionare una query nell'elenco a discesa per visualizzare gli aggiornamenti immediati, quindi selezionare Salva per applicare le modifiche.

Passaggio 8: Aggiungere opzioni di configurazione avanzate

Estendere il widget con funzionalità di configurazione predefinite, ad esempio nomi e dimensioni personalizzati.

Abilitare la configurazione del nome e delle dimensioni

Azure DevOps offre due funzionalità configurabili predefinite:

Caratteristica / Funzionalità Proprietà del manifesto Scopo
Nomi personalizzati isNameConfigurable: true Gli utenti possono eseguire l'override del nome del widget predefinito
Dimensioni multiple Molteplici supportedSizes voci Gli utenti possono ridimensionare i widget

Esempio di manifesto migliorato

{
    "contributions": [
        {
             "id": "HelloWorldWidget3",
             "type": "ms.vss-dashboards-web.widget",
             "targets": [
                 "ms.vss-dashboards-web.widget-catalog",  
                 "fabrikam.azuredevops-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"]
             }
         }
    ]
}

Visualizzare i nomi configurati

Per visualizzare i nomi dei widget personalizzati, aggiornare il widget per usare widgetSettings.name:

return {
    load: function (widgetSettings) {
        // Display configured name instead of hard-coded text
        var $title = $('h2.title');
        $title.text(widgetSettings.name);

        return getQueryInfo(widgetSettings);
    },
    reload: function (widgetSettings) {
        // Update name during configuration changes
        var $title = $('h2.title');
        $title.text(widgetSettings.name);

        return getQueryInfo(widgetSettings);
    }
}

Dopo aver aggiornato l'estensione, è possibile configurare sia il nome del widget che le dimensioni:

Screenshot che mostra dove è possibile configurare il nome e le dimensioni del widget.

Ripacchetto e aggiornare l'estensione per abilitare queste opzioni di configurazione avanzate.

Congratulazioni! È stato creato un widget completo e configurabile del dashboard di Azure DevOps con funzionalità di anteprima in tempo reale e opzioni di personalizzazione degli utenti.