Partager via


Ajouter un widget de tableau de bord

Azure DevOps Services | Azure DevOps Server | Azure DevOps Server 2022

Les widgets sont implémentés sous forme de contributions dans l’infrastructure d’extension. Une seule extension peut inclure plusieurs contributions de widget. Cet article explique comment créer une extension qui fournit un ou plusieurs widgets.

Conseil

Consultez notre documentation la plus récente sur le développement d’extensions à l’aide du Kit de développement logiciel (SDK) d’extension Azure DevOps.

Conseil

Si vous démarrez une nouvelle extension Azure DevOps, essayez d'abord ces collections d'exemples maintenues : elles fonctionnent avec les builds de produit actuels et couvrent les scénarios modernes (par exemple, en ajoutant des onglets sur les pages de requête de tirage).

Si un exemple ne fonctionne pas dans votre organisation, installez-le dans une organisation personnelle ou de test et comparez les ID cibles et les versions d’API du manifeste d’extension avec les documents actuels. Pour obtenir des informations de référence et des API, consultez :

Prérequis

Besoin Descriptif
Connaissances en programmation Connaissances JavaScript, HTML et CSS pour le développement de widgets
Organisation Azure DevOps Créez une organisation.
Éditeur de texte Nous utilisons Visual Studio Code pour les didacticiels
Node.JS Dernière version de Node.js
Interface CLI multiplateforme tfx-cli pour empaqueter des extensions
Installer à l’aide de : npm i -g tfx-cli
Répertoire du projet Répertoire de base avec cette structure après avoir suivi le tutoriel :

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

Vue d’ensemble du didacticiel

Ce tutoriel explique le développement de widgets par trois exemples progressifs :

Partie Focus Contenu
Partie 1 : Hello World Création de widget de base Créer un widget qui affiche du texte
Partie 2 : Intégration de l’API REST Appels d’API Azure DevOps Ajouter des fonctionnalités d’API REST pour extraire et afficher des données
Partie 3 : Configuration du widget Personnalisation utilisateur Implémenter des options de configuration pour votre widget

Conseil

Si vous préférez passer directement aux exemples de travail, les exemples inclus (voir la note précédente) affichent un ensemble de widgets que vous pouvez empaqueter et publier.

Avant de commencer, passez en revue les styles de widget de base et les conseils structurels que nous fournissons.

Partie 1 : Hello World

Créez un widget de base qui affiche « Hello World » à l’aide de JavaScript. Cette base illustre les principaux concepts de développement de widgets.

Capture d’écran du tableau de bord Vue d’ensemble avec un exemple de widget.

Étape 1 : Installer le Kit de développement logiciel (SDK) client

Le Kit de développement logiciel (SDK) VSS permet à votre widget de communiquer avec Azure DevOps. Installez-le à l’aide de npm :

npm install vss-web-extension-sdk

Copiez le fichier VSS.SDK.min.js de vss-web-extension-sdk/lib vers votre dossier home/sdk/scripts.

Pour plus de documentation sur le SDK, consultez la page GitHub du Kit de développement logiciel (SDK) client.

Étape 2 : Créer la structure HTML

Créez hello-world.html dans votre répertoire de projet. Ce fichier fournit la disposition et les références du widget aux scripts requis.

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

Les widgets s’exécutent dans des iframes, donc la plupart des éléments principaux HTML sauf <script> et <link> sont ignorés par l’infrastructure.

Étape 3 : Ajouter un widget JavaScript

Pour implémenter la fonctionnalité de widget, ajoutez ce script à la <head> section de votre fichier 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>

Composants JavaScript clés

Fonction Objectif
VSS.init() Initialise la communication entre le widget et Azure DevOps
VSS.require() Charge les bibliothèques de SDK requises et les widgets d’assistance
VSS.register() Inscrit votre widget avec un identificateur unique
WidgetHelpers.IncludeWidgetStyles() Applique le style Azure DevOps par défaut
VSS.notifyLoadSucceeded() Notifie le framework que le chargement a été effectué avec succès

Importante

Le nom du widget dans VSS.register() doit correspondre au id le manifeste de votre extension (étape 5).

Étape 4 : Ajouter des images d’extension

Créez les images requises pour votre extension :

  • Logo d’extension : image de 98 x 98 pixels nommée logo.png dans le img dossier
  • Icône de catalogue de widgets : image de 98 x 98 pixels nommée CatalogIcon.png dans le img dossier
  • Aperçu du widget : image de 330 x 160 pixels nommée preview.png dans le img dossier

Ces images s’affichent dans la Place de marché et le catalogue de widgets lorsque les utilisateurs parcourent les extensions disponibles.

Étape 5 : Créer le manifeste d’extension

Créez vss-extension.json dans le répertoire racine de votre projet. Ce fichier définit les métadonnées et les contributions de votre extension :

{
    "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

Remplacez par "publisher": "fabrikam" votre nom d’éditeur réel. Découvrez comment créer un éditeur.

Propriétés de manifeste essentielles

Section Objectif
Informations de base Nom de l’extension, version, description et éditeur
icônes Chemins d’accès aux ressources visuelles de votre extension
Contributions Définitions de widgets, notamment l’ID, le type et les propriétés
Fichiers Tous les fichiers à inclure dans le package d’extension

Pour obtenir la documentation complète du manifeste, consultez la référence du manifeste d’extension.

Étape 6 : Empaqueter et publier votre extension

Empaqueter votre extension et la publier sur Visual Studio Marketplace.

Installer l’outil d’empaquetage

npm i -g tfx-cli

Créer votre package d’extension

À partir de votre répertoire de projet, exécutez :

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

Cette action crée un .vsix fichier qui contient votre extension empaquetée.

Configurer un éditeur

  1. Accédez au portail de publication Visual Studio Marketplace.
  2. Connectez-vous et créez un éditeur si vous n’en avez pas.
  3. Choisissez un identificateur d’éditeur unique (utilisé dans votre fichier manifeste).
  4. Mettez à jour votre vss-extension.json pour utiliser votre nom d'éditeur au lieu de « fabrikam ».

Charger votre extension

  1. Dans le portail de publication, sélectionnez Charger une nouvelle extension.
  2. Choisissez votre .vsix fichier et chargez-le.
  3. Partagez l’extension avec votre organisation Azure DevOps.

Vous pouvez également utiliser la ligne de commande :

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

Conseil

Permet --rev-version d’incrémenter automatiquement le numéro de version lors de la mise à jour d’une extension existante.

Étape 7 : Installer et tester votre widget

Pour tester, ajoutez votre widget à un tableau de bord :

  1. Accédez à votre projet Azure DevOps : https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Rendez-vous dans Vue d’ensemble>Tableaux de bord.
  3. Sélectionnez Ajouter un widget.
  4. Recherchez votre widget dans le catalogue, puis sélectionnez Ajouter.

Votre widget « Hello World » s’affiche sur le tableau de bord, affichant le texte que vous avez configuré.

Étape suivante : Passez à la partie 2 pour apprendre à intégrer des API REST Azure DevOps dans votre widget.

Partie 2 : Hello World avec l’API REST Azure DevOps

Étendez votre widget pour interagir avec les données Azure DevOps à l’aide d’API REST. Cet exemple montre comment extraire des informations de requête et l’afficher dynamiquement dans votre widget.

Dans cette partie, utilisez l’API REST Suivi des éléments de travail pour récupérer des informations sur une requête existante et afficher les détails de la requête sous le texte « Hello World ».

Capture d’écran du tableau de bord Vue d’ensemble avec un exemple de widget utilisant l’API REST pour WorkItemTracking.

Étape 1 : Créer le fichier HTML amélioré

Créez un fichier de widget qui s’appuie sur l’exemple précédent. Copiez et renommez-le hello-world.html en hello-world2.html. Votre structure de projet inclut désormais les éléments suivants :

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

Mettre à jour la structure HTML du widget

Apportez ces modifications à hello-world2.html:

  1. Ajoutez un conteneur pour les données de requête : incluez un nouvel <div> élément pour afficher les informations de requête.
  2. Mettez à jour l’identificateur du widget : changez le nom du widget HelloWorldWidget par HelloWorldWidget2 pour une identification unique.
<!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>

Étape 2 : Configurer les autorisations d’accès aux API

Avant d’effectuer des appels d’API REST, configurez les autorisations requises dans votre manifeste d’extension.

Ajouter l’étendue de travail

L’étendue vso.work accorde un accès en lecture seule aux éléments de travail et aux requêtes. Ajoutez cette étendue à votre vss-extension.json :

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

Exemple de manifeste complet

Pour obtenir un manifeste complet avec d’autres propriétés, structurez-le comme suit :

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

Importante

Limitations de l’étendue : l’ajout ou la modification d’étendues après la publication n’est pas prise en charge. Si vous avez déjà publié votre extension, vous devez d’abord la supprimer de la Place de marché. Accédez au portail de publication visual Studio Marketplace, recherchez votre extension, puis sélectionnez Supprimer.

Étape 3 : Implémenter l’intégration de l’API REST

Azure DevOps fournit des bibliothèques de client REST JavaScript via le Kit de développement logiciel (SDK). Ces bibliothèques encapsulent les appels AJAX et mappent les réponses d’API aux objets utilisables.

Mettre à jour le widget JavaScript

Remplacez l'appel VSS.require dans votre hello-world2.html pour inclure le client REST de suivi des éléments de travail :

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

Détails de l’implémentation clé

Composant Objectif
WorkItemTrackingRestClient.getClient() Obtient une instance du client REST Work Item Tracking
getQuery() Récupère les informations de requête encapsulées dans une promesse
WidgetStatusHelper.Failure() Fournit une gestion cohérente des erreurs pour les défaillances de widgets
projectId Contexte de projet actuel requis pour les appels d’API

Conseil

Chemins de requête personnalisés : si vous n’avez pas de requête « Commentaires » dans « Requêtes partagées », remplacez "Shared Queries/Feedback" par le chemin d’accès à une requête qui existe dans votre projet.

Étape 4 : Afficher les données de réponse de l’API

Affichez les informations de requête dans votre widget en traitant la réponse de l’API REST.

Ajouter un rendu des données de requête

Remplacez le // Process query data commentaire par cette implémentation :

// 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);

La getQuery() méthode retourne un Contracts.QueryHierarchyItem objet avec des propriétés pour les métadonnées de requête. Cet exemple montre comment afficher trois éléments clés d’informations sous le texte « Hello World ».

Exemple de travail complet

Votre fichier final hello-world2.html doit ressembler à ceci :

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

Étape 5 : Mettre à jour le manifeste d’extension

Pour le rendre disponible dans le catalogue de widgets, ajoutez votre nouveau widget au manifeste d’extension.

Ajouter la deuxième contribution du widget

Mettez à jour vss-extension.json pour inclure votre widget COMPATIBLE AVEC l’API REST. Ajoutez cette contribution au contributions tableau :

{
    "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"
    ]
}

Conseil

Image d’aperçu : créez une preview2.png image (330 x 160 pixels) et placez-la dans le img dossier pour afficher aux utilisateurs l’apparence de votre widget dans le catalogue.

Étape 6 : Empaqueter, publier et partager

Créez un package, publiez et partagez votre extension. Si vous avez déjà publié l’extension, vous pouvez repackager et la mettre à jour directement sur la Place de marché.

Étape 7 : Tester votre widget d’API REST

Pour afficher l’intégration de l’API REST en action, ajoutez le nouveau widget à votre tableau de bord :

  1. Accédez à votre projet Azure DevOps : https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Veuillez sélectionner Aperçu des >tableaux de bord.
  3. Sélectionnez Ajouter un widget.
  4. Recherchez « Hello World Widget 2 (avec API) », puis sélectionnez Ajouter.

Votre widget amélioré affiche à la fois le texte « Hello World » et les informations de requête dynamiques de votre projet Azure DevOps.

Étapes suivantes : passez à la partie 3 pour ajouter des options de configuration qui permettent aux utilisateurs de personnaliser la requête à afficher.

Partie 3 : Configurer Hello World

Enrichissez la partie 2 en ajoutant des options de configuration utilisateur à votre widget. Au lieu de coder en dur le chemin de requête, créez une interface de configuration qui permet aux utilisateurs de sélectionner la requête à afficher, avec la fonctionnalité d’aperçu en direct.

Cette partie montre comment créer des widgets configurables que les utilisateurs peuvent personnaliser en fonction de leurs besoins spécifiques tout en fournissant des commentaires en temps réel pendant la configuration.

Capture d'écran de la préversion en direct du tableau de bord Aperçu du widget basé sur les modifications.

Étape 1 : Créer des fichiers de configuration

Les configurations de widget partagent de nombreuses similitudes avec les widgets eux-mêmes : elles utilisent les mêmes sdk, la même structure HTML et les modèles JavaScript, mais servent des objectifs différents dans l’infrastructure d’extension.

Configurer la structure du projet

Pour prendre en charge la configuration du widget, créez deux nouveaux fichiers :

  1. Copiez hello-world2.html et renommez-le en hello-world3.html, votre widget configurable.
  2. Créez un fichier appelé configuration.html, qui gère l’interface de configuration.

Votre structure de projet inclut désormais les éléments suivants :

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

Créer l’interface de configuration

Ajoutez cette structure HTML à configuration.html, ce qui crée un sélecteur de liste déroulante pour choisir des requêtes :

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

Étape 2 : Implémenter la configuration JavaScript

Configuration JavaScript suit le même modèle d’initialisation que les widgets, mais implémente le IWidgetConfiguration contrat au lieu du contrat de base IWidget .

Ajouter une logique de configuration

Insérez ce script dans la <head> section suivante :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>

Détails du contrat de configuration

Le IWidgetConfiguration contrat nécessite ces fonctions clés :

Fonction Objectif Moment de l’appel
load() Initialiser l’interface utilisateur de configuration avec les paramètres existants Lorsque la boîte de dialogue de configuration s’ouvre
onSave() Sérialiser les paramètres d’entrée utilisateur et de validation Lorsque l’utilisateur sélectionne Enregistrer

Conseil

Sérialisation des données : cet exemple utilise JSON pour sérialiser les paramètres. Le widget accède à ces paramètres via widgetSettings.customSettings.data et doit les désérialiser en conséquence.

Étape 3 : Activer la fonctionnalité d’aperçu en direct

La préversion dynamique permet aux utilisateurs d’afficher les modifications du widget immédiatement quand ils modifient les paramètres de configuration, en fournissant des commentaires instantanés avant l’enregistrement.

Implémenter des notifications de modification

Pour activer la préversion en direct, ajoutez ce gestionnaire d’événements dans la load fonction :

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

Terminer le fichier de configuration

Votre résultat final configuration.html doit ressembler à ceci :

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

Activer le bouton Enregistrer : l’infrastructure nécessite au moins une notification de modification de configuration pour activer le bouton Enregistrer . Le gestionnaire d’événements de modification garantit que cette action se produit lorsque les utilisateurs sélectionnent une option.

Étape 4 : Rendre le widget configurable

Transformez votre widget de la partie 2 pour utiliser des données de configuration au lieu de valeurs codées en dur. Cette étape nécessite l’implémentation du IConfigurableWidget contrat.

Mettre à jour l’inscription des widgets

Dans hello-world3.html, apportez ces modifications :

  1. Mettre à jour l’ID du widget : passer de HelloWorldWidget2 à HelloWorldWidget3.
  2. Ajouter une fonction de rechargement : implémentez le IConfigurableWidget contrat.
return {
    load: function (widgetSettings) {
        // Set your title
        var $title = $('h2.title');
        $title.text('Hello World');

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

Gérer les données de configuration

Mettez à jour la getQueryInfo fonction pour utiliser les paramètres de configuration au lieu des chemins de requête codés en dur :

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

Différences de cycle de vie des widgets

Fonction Objectif Instructions d’utilisation
load() Rendu initial du widget et configuration ponctuelle Opérations lourdes, initialisation des ressources
reload() Mettre à jour le widget avec une nouvelle configuration Mises à jour légères, actualisation des données

Conseil

Optimisation des performances : utilisez load() pour les opérations coûteuses qui n’ont besoin d’être exécutées qu’une seule fois et reload() pour les mises à jour rapides lorsque la configuration change.

(Facultatif) Ajouter un lightbox pour obtenir des informations détaillées

Les widgets de tableau de bord ont un espace limité, ce qui rend difficile l’affichage d’informations complètes. Un lightbox fournit une solution élégante en affichant des données détaillées dans une superposition modale sans naviguer loin du tableau de bord.

Pourquoi utiliser un lightbox dans des widgets ?

Avantage Descriptif
Optimisation de l’espace Conserver le widget compact tout en offrant des vues détaillées
Expérience utilisateur Gérer le contexte du tableau de bord tout en affichant plus d’informations
Affichage progressif Afficher les données récapitulatives dans le widget, détails à la demande
Conception réactive Adapter à différentes tailles d’écran et configurations de widget

Implémenter des éléments cliquables

Mettez à jour votre rendu des données de requête pour inclure des éléments cliquables qui déclenchent la boîte de réception :

// 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);

Créer la fonctionnalité lightbox

Ajoutez cette implémentation lightbox à votre 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');
        }
    });
}

Ajoutez un style de type « lightbox »

Incluez des styles CSS pour la zone de lumière dans la section HTML <head> de votre 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>

Implémentation améliorée du widget

Votre widget amélioré complet avec la fonctionnalité 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>

Considérations relatives à l’accessibilité : assurez-vous que votre lightbox est accessible au clavier et inclut des étiquettes appropriées pour les lecteurs d’écran. Testez avec les fonctionnalités d’accessibilité intégrées d’Azure DevOps.

Importante

Performances : les « lightboxes » doivent se charger rapidement. Envisagez le chargement différé des données détaillées uniquement à l’ouverture de la « lightbox », au lieu de tout charger dès le départ.

Étape 5 : Configurer le manifeste d’extension

Inscrivez le widget configurable et son interface de configuration dans votre manifeste d’extension.

Ajoutez des contributions de widget et de configuration

Mise à jour vss-extension.json pour inclure deux nouvelles contributions :

{
    "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
                    },
                    {
                        "rowSpan": 2,
                        "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
        }
    ]
}

Exigences de contribution à la configuration

Propriété Objectif Valeur requise
type Identifie la contribution en tant que configuration de widget ms.vss-dashboards-web.widget-configuration
targets Où la configuration s’affiche ms.vss-dashboards-web.widget-configuration
uri Chemin d’accès au fichier HTML de configuration Chemin de votre fichier de configuration

Modèle de ciblage de widget

Pour les widgets configurables, le targets tableau doit inclure une référence à la configuration :

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

Avertissement

Visibilité du bouton de configuration : si le widget ne cible pas correctement sa contribution de configuration, le bouton Configurer n’apparaît pas. Vérifiez que les noms de l’éditeur et de l’extension correspondent exactement à votre manifeste.

Étape 6 : Empaqueter, publier et partager

Déployez votre extension améliorée avec des fonctionnalités de configuration.

S’il s’agit de votre première publication, suivez l’étape 6 : Empaqueter, publier et partager. Pour les extensions existantes, repackagez et mettez à jour directement dans la Place de marché.

Étape 7 : Tester le widget configurable

Découvrez le flux de travail de configuration complet en ajoutant et en configurant votre widget.

Ajoutez le widget à votre tableau de bord.

  1. Accédez à https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Rendez-vous dans Vue d’ensemble>Tableaux de bord.
  3. Sélectionnez Ajouter un widget.
  4. Recherchez « Hello World Widget 3 (avec configuration) », puis sélectionnez Ajouter.

Une invite de configuration s’affiche, car le widget nécessite une configuration :

Capture d’écran du tableau de bord Vue d’ensemble avec un exemple de widget à partir du catalogue.

Configurer le widget

Configuration d’accès via l’une ou l’autre méthode :

  • Menu widget : pointez sur le widget, sélectionnez les points de suspension (⋯), puis configurez
  • Mode d’édition du tableau de bord : sélectionnez Modifier dans le tableau de bord, puis le bouton configurer sur le widget

Le panneau de configuration s’ouvre avec un aperçu en direct au centre. Sélectionnez une requête dans la liste déroulante pour afficher les mises à jour immédiates, puis sélectionnez Enregistrer pour appliquer vos modifications.

Étape 8 : Ajouter des options de configuration avancées

Étendez votre widget avec des fonctionnalités de configuration intégrées telles que des noms et des tailles personnalisés.

Activer la configuration du nom et de la taille

Azure DevOps fournit deux fonctionnalités configurables prêtes à l’emploi :

Caractéristique Propriété du manifeste Objectif
Noms personnalisés isNameConfigurable: true Les utilisateurs peuvent remplacer le nom du widget par défaut
Tailles multiples Plusieurs entrées supportedSizes Les utilisateurs peuvent redimensionner des widgets

Exemple enrichi de manifeste

{
    "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"]
             }
         }
    ]
}

Afficher les noms configurés

Pour afficher les noms de widgets personnalisés, mettez à jour votre widget pour utiliser 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);
    }
}

Après avoir mis à jour votre extension, vous pouvez configurer le nom et la taille du widget :

Capture d’écran montrant où le nom et la taille du widget peuvent être configurés.

Repackagez et mettez à jour votre extension pour activer ces options de configuration avancées.

Félicitations! Vous avez créé un widget de tableau de bord Azure DevOps complet et configurable avec des fonctionnalités d’aperçu en direct et des options de personnalisation utilisateur.