Ajouter un widget de tableau de bord

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

Les widgets d’un tableau de bord sont implémentés en tant que contributions dans l’infrastructure d’extension. Une seule extension peut avoir plusieurs contributions. Découvrez comment créer une extension avec plusieurs widgets en tant que contributions.

Cet article est divisé en trois parties, chacune s’appuyant sur le précédent , commençant par un widget simple et se terminant par un widget complet.

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.

Préparation et configuration requise pour ce tutoriel

Pour créer des extensions pour Azure DevOps ou TFS, vous avez besoin de certains logiciels et outils requis :

Connaissances: Certaines connaissances en JavaScript, HTML et CSS sont requises pour le développement de widgets.

  • Une organisation dans Azure DevOps pour l’installation et le test de votre widget. Plus d’informations sont disponibles ici
  • Un éditeur de texte. Pour la plupart des tutoriels, nous avons utilisé Visual Studio Code, qui peut être téléchargé ici
  • La dernière version du nœud, qui peut être téléchargée ici
  • CLI multiplateforme pour Azure DevOps (tfx-cli) pour empaqueter vos extensions.
    • tfx-cli peut être installé à l’aide npmde , un composant de Node.js en exécutant npm i -g tfx-cli
  • Répertoire de base de votre projet. Ce répertoire est appelé tout home au long du tutoriel.

Structure du fichier d’extension :

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

Ce que vous trouverez dans le tutoriel

  1. La première partie de ce guide vous montre comment créer un widget, qui imprime un simple message « Hello World ».
  2. La deuxième partie s’appuie sur la première en ajoutant un appel à une API REST Azure DevOps.
  3. La troisième partie explique comment ajouter une configuration à votre widget.

Notes

Si vous êtes pressé et que vous souhaitez mettre la main sur le code immédiatement, vous pouvez télécharger les exemples ici. Une fois téléchargé, accédez au widgets dossier, puis suivez les étapes 6 et 7 directement pour publier l’exemple d’extension qui contient les trois exemples de widgets de complexité variable.

Commencez avec quelques styles de base pour les widgets que nous fournissons dès la boîte et quelques conseils sur la structure des widgets.

Partie 1 : Hello World

Cette partie présente un widget qui imprime « Hello World » à l’aide de JavaScript.

Overview dashboard with a sample widget

Étape 1 : Obtenir le Kit de développement logiciel (SDK) client - VSS.SDK.min.js

Le script principal du KIT de développement logiciel ( VSS.SDK.min.jsSDK), , permet aux extensions web de communiquer avec le frame Azure DevOps hôte. Le script effectue des opérations telles que l’initialisation, la notification que l’extension est chargée ou l’obtention du contexte sur la page active. Obtenez le fichier du Kit de développement logiciel (SDK) VSS.SDK.min.js client et ajoutez-le à votre application web. Placez-le dans le home/sdk/scripts dossier.

Utilisez la commande « npm install » pour récupérer le KIT de développement logiciel (SDK) :

npm install vss-web-extension-sdk

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

Étape 2 : Votre page HTML - hello-world.html

Votre page HTML est le collage qui maintient votre disposition ensemble et inclut des références à CSS et JavaScript. Vous pouvez nommer ce fichier n’importe quoi. Veillez simplement à mettre à jour toutes les références avec hello-world le nom que vous utilisez.

Votre widget est basé sur du CODE HTML et est hébergé dans un iframe. Ajoutez le code HTML ci-dessous dans hello-world.html. Nous ajoutons la référence obligatoire au VSS.SDK.min.js fichier et incluons un h2 élément dans , qui est mis à jour avec la chaîne Hello World à l’étape à venir.

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

Même si nous utilisons un fichier HTML, la plupart des éléments principaux HTML autres que le script et le lien sont ignorés par l’infrastructure.

Étape 3 : votre code JavaScript

Nous utilisons JavaScript pour afficher le contenu dans le widget. Dans cet article, nous encapsulons tout notre code JavaScript à l’intérieur d’un &lt;script&gt; élément dans le fichier HTML. Vous pouvez choisir d’avoir ce code dans un fichier JavaScript distinct et de le référencer dans le fichier HTML. Le code restitue le contenu. Ce code JavaScript initialise également le Kit de développement logiciel (SDK) VSS, mappe le code de votre widget au nom de votre widget et avertit l’infrastructure d’extension des réussites ou des échecs du widget. Dans notre cas, vous trouverez ci-dessous le code qui imprimerait « Hello World » dans le widget. Ajoutez cet script élément dans le head du code HTML.

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

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

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

VSS.init initialise la négociation entre l’iframe hébergeant le widget et le cadre hôte. Nous passons explicitNotifyLoaded: true afin que le widget puisse informer explicitement l’hôte lorsque nous avons terminé le chargement. Ce contrôle nous permet d’avertir l’achèvement du chargement après avoir vérifié que les modules dépendants sont chargés. Nous passons afin que les styles principaux Azure DevOps pour les usePlatformStyles: true éléments html (tels que body, div, etc.) puissent être utilisés par le widget. Si le widget préfère ne pas utiliser ces styles, il peut transmettre usePlatformStyles: false.

VSS.require est utilisé pour charger les bibliothèques de scripts VSS requises. Un appel à cette méthode charge automatiquement les bibliothèques générales telles que JQuery et JQueryUI. Dans notre cas, nous dépendons de la bibliothèque WidgetHelpers, qui est utilisée pour communiquer l’état du widget à l’infrastructure du widget. Nous passons donc le nom TFS/Dashboards/WidgetHelpers du module correspondant et un rappel à VSS.require. Le rappel est appelé une fois le module chargé. Le rappel contient le reste du code JavaScript nécessaire pour le widget. À la fin du rappel, nous appelons VSS.notifyLoadSucceeded pour notifier l’achèvement du chargement.

WidgetHelpers.IncludeWidgetStyles inclut une feuille de style avec des css de base pour vous aider à démarrer. Veillez à encapsuler votre contenu à l’intérieur d’un élément HTML avec la classe widget pour utiliser ces styles.

VSS.register est utilisé pour mapper une fonction en JavaScript, qui identifie de manière unique le widget parmi les différentes contributions de votre extension. Le nom doit correspondre à celui id qui identifie votre contribution, comme décrit à l’étape 5. Pour les widgets, la fonction passée à doit renvoyer VSS.register un objet qui satisfait au IWidget contrat, par exemple, l’objet retourné doit avoir une propriété de chargement dont la valeur est une autre fonction qui a la logique principale pour restituer le widget. Dans notre cas, il s’agit de mettre à jour le texte de l’élément h2 sur « Hello World ». C’est cette fonction qui est appelée lorsque l’infrastructure de widget instancie votre widget. Nous utilisons le WidgetStatusHelper de WidgetHelpers pour renvoyer le WidgetStatus en tant que réussite.

Avertissement

Si le nom utilisé pour inscrire le widget ne correspond pas à l’ID de la contribution dans le manifeste, le widget fonctionne de manière inattendue.

doit vss-extension.json toujours se trouver à la racine du dossier (dans ce guide, HelloWorld). Pour tous les autres fichiers, vous pouvez les placer dans la structure souhaitée à l’intérieur du dossier. Veillez simplement à mettre à jour les références de manière appropriée dans les fichiers HTML et dans le vss-extension.json manifeste.

Étape 4 : logo de votre extension : logo.png

Votre logo s’affiche dans la Place de marché et dans le catalogue de widgets une fois qu’un utilisateur a installé votre extension.

Vous avez besoin d’une icône de catalogue de 98 px x 98 px. Choisissez une image, nommez-la logo.pnget placez-la dans le img dossier.

Pour prendre en charge TFS 2015 Update 3, vous avez besoin d’une image supplémentaire de 330 px x 160 px. Cette image d’aperçu s’affiche dans ce catalogue. Choisissez une image, nommez-la preview.pnget placez-la dans le img dossier comme précédemment.

Vous pouvez nommer ces images comme vous le souhaitez tant que le manifeste d’extension à l’étape suivante est mis à jour avec les noms que vous utilisez.

Étape 5 : Manifeste de votre extension : vss-extension.json

Créez un fichier json (vss-extension.jsonpar exemple) dans le home répertoire avec le contenu suivant :

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

Pour plus d’informations sur les attributs obligatoires, consultez la référence du manifeste d’extension

Notes

L’éditeur doit être remplacé par votre nom d’éditeur. Pour créer un éditeur maintenant, consultez Package/Publier/Installer.

Icônes

La strophe d’icônes spécifie le chemin d’accès à l’icône de votre extension dans votre manifeste.

Contributions

Chaque entrée de contribution définit des propriétés.

  • ID permettant d’identifier votre contribution. Cet ID doit être unique au sein d’une extension. Cet ID doit correspondre au nom que vous avez utilisé à l’étape 3 pour inscrire votre widget.
  • Type de contribution. Pour tous les widgets, le type doit être ms.vss-dashboards-web.widget.
  • Tableau des cibles auxquelles la contribution contribue. Pour tous les widgets, la cible doit être [ms.vss-dashboards-web.widget-catalog].
  • Les propriétés sont des objets qui incluent des propriétés pour le type de contribution. Pour les widgets, les propriétés suivantes sont obligatoires.
Propriété Description
name Nom du widget à afficher dans le catalogue de widgets.
description Description du widget à afficher dans le catalogue de widgets.
catalogIconUrl Chemin relatif de l’icône de catalogue que vous avez ajoutée à l’étape 4 pour l’afficher dans le catalogue de widgets. L’image doit être de 98 px x 98 px. Si vous avez utilisé une autre structure de dossiers ou un nom de fichier différent, spécifiez ici le chemin d’accès relatif approprié.
previewImageUrl Chemin relatif de l’image d’aperçu que vous avez ajoutée à l’étape 4 pour l’afficher dans le catalogue de widgets pour TFS 2015 Update 3 uniquement. L’image doit être de 330 px x 160 px. Si vous avez utilisé une autre structure de dossiers ou un nom de fichier différent, spécifiez ici le chemin d’accès relatif approprié.
URI Chemin relatif du fichier HTML que vous avez ajouté à l’étape 1. Si vous avez utilisé une autre structure de dossiers ou un nom de fichier différent, spécifiez ici le chemin d’accès relatif approprié.
supportedSizes Tableau de tailles prises en charge par votre widget. Lorsqu’un widget prend en charge plusieurs tailles, la première taille du tableau est la taille par défaut du widget. widget size est spécifié pour les lignes et les colonnes occupées par le widget dans la grille du tableau de bord. Une ligne/colonne correspond à 160 px. Toute dimension supérieure à 1x1 obtient 10 px supplémentaires qui représentent la gouttière entre les widgets. Par exemple, un widget 3x2 est 160*3+10*2 large et 160*2+10*1 haut. La taille maximale prise en charge est 4x4.
supportedScopes Pour le moment, nous prenons uniquement en charge les tableaux de bord d’équipe. La valeur doit être project_team. À l’avenir, lorsque nous prenons en charge d’autres étendues de tableau de bord, il y aura d’autres options à choisir ici.

Fichiers

La strophe de fichiers indique les fichiers que vous souhaitez inclure dans votre package : votre page HTML, vos scripts, le script sdk et votre logo. Définissez sur addressable , true sauf si vous incluez d’autres fichiers qui n’ont pas besoin d’être adressables à l’URL.

Notes

Pour plus d’informations sur le fichier manifeste d’extension, comme ses propriétés et leur action, consultez la référence du manifeste d’extension.

Étape 6 : Empaqueter, publier et partager

Une fois que vous avez écrit votre extension, l’étape suivante pour l’obtenir dans la Place de marché consiste à empaqueter tous vos fichiers ensemble. Toutes les extensions sont empaquetées sous forme de fichiers .vsix compatibles avec VSIX 2.0 . Microsoft fournit une interface de ligne de commande (CLI) multiplateforme pour empaqueter votre extension.

Obtenir l’outil d’empaquetage

Vous pouvez installer ou mettre à jour l’interface CLI multiplateforme pour Azure DevOps (tfx-cli) à l’aide npmde , un composant de Node.js, à partir de votre ligne de commande.

npm i -g tfx-cli

Empaqueter votre extension

L’empaquetage de votre extension dans un fichier .vsix est sans effort une fois que vous avez le tfx-cli. Accédez au répertoire de base de votre extension et exécutez la commande suivante.

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

Notes

La version d’une extension/intégration doit être incrémentée à chaque mise à jour.
Lors de la mise à jour d’une extension existante, mettez à jour la version dans le manifeste ou passez le commutateur de ligne de --rev-version commande. Cela incrémente le numéro de version de correctif de votre extension et enregistre la nouvelle version dans votre manifeste.

Une fois que vous avez votre extension empaquetée dans un fichier .vsix, vous êtes prêt à publier votre extension sur la Place de marché.

Créer un serveur de publication pour l’extension

Toutes les extensions, y compris les extensions de Microsoft, sont identifiées comme étant fournies par un éditeur. Si vous n’êtes pas déjà membre d’un éditeur existant, vous en créerez un.

  1. Connectez-vous au portail de publication de la Place de marché Visual Studio
  2. Si vous n’êtes pas déjà membre d’un éditeur existant, vous serez invité à créer un éditeur. Si vous n’êtes pas invité à créer un éditeur, faites défiler vers le bas de la page et sélectionnez Publier les extensions sous Sites associés.
    • Spécifiez un identificateur pour votre éditeur, par exemple : mycompany-myteam
      • L’identificateur est utilisé comme valeur pour l’attribut dans le publisher fichier manifeste de vos extensions.
    • Spécifiez un nom d’affichage pour votre éditeur, par exemple : My Team
  3. Passez en revue le Contrat d’éditeur de la Place de marché et sélectionnez Créer

Votre éditeur est maintenant défini. Dans une version ultérieure, vous pouvez accorder des autorisations pour afficher et gérer les extensions de votre éditeur. Il est facile et plus sécurisé pour les équipes et les organisations de publier des extensions sous un éditeur commun, mais sans avoir à partager un ensemble d’informations d’identification entre un ensemble d’utilisateurs.

Mettez à jour le vss-extension.json fichier manifeste dans les exemples pour remplacer l’ID fabrikam d’éditeur factice par votre ID d’éditeur.

Publier et partager l’extension

Après avoir créé un éditeur, vous pouvez maintenant charger votre extension sur la Place de marché.

  1. Recherchez le bouton Charger une nouvelle extension , accédez à votre fichier .vsix empaqueté, puis sélectionnez Charger.

Vous pouvez également charger votre extension via la ligne de commande à l’aide de la tfx extension publish commande au lieu de tfx extension create empaqueter et publier votre extension en une seule étape. Vous pouvez éventuellement utiliser --share-with pour partager votre extension avec un ou plusieurs comptes après la publication. Vous aurez également besoin d’un jeton d’accès personnel.

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

Étape 7 : Ajouter un widget à partir du catalogue

  1. Accédez à votre projet dans Azure DevOps, http://dev.azure.com/{yourOrganization}/{yourProject}

  2. Sélectionnez Vue d’ensemble, puis Tableaux de bord.

  3. Choisissez Ajouter un widget.

  4. Mettez en surbrillance votre widget, puis sélectionnez Ajouter.

    Le widget s’affiche sur votre tableau de bord.

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

Les widgets peuvent appeler n’importe quelle API REST dans Azure DevOps pour interagir avec les ressources Azure DevOps. Dans cet exemple, nous utilisons l’API REST pour WorkItemTracking pour extraire des informations sur une requête existante et afficher certaines informations de requête dans le widget juste en dessous du texte « Hello World ».

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

Étape 1 : HTML

Copiez le fichier hello-world.html de l’exemple précédent, puis renommez la copie en hello-world2.html. Votre dossier ressemble maintenant à ce qui suit :

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

Ajoutez un nouvel élément 'div' juste en dessous de 'h2' pour contenir les informations de requête. Mettez à jour le nom du widget de « HelloWorldWidget » sur « HelloWorldWidget2 » dans la ligne où vous appelez « VSS.register ». Cela permet à l’infrastructure d’identifier de manière unique le widget au sein de l’extension.
<!DOCTYPE html>
<html>
    <head>                          
        <script src="sdk/scripts/VSS.SDK.min.js"></script>              
        <script type="text/javascript">
            VSS.init({
                explicitNotifyLoaded: true,
                usePlatformStyles: true
            });

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

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

Étape 2 : Accéder aux ressources Azure DevOps

Pour activer l’accès aux ressources Azure DevOps, les étendues doivent être spécifiées dans le manifeste de l’extension. Nous ajoutons l’étendue vso.work à notre manifeste.
Cette étendue indique que le widget a besoin d’un accès en lecture seule aux requêtes et aux éléments de travail. Consultez toutes les étendues disponibles ici. Ajoutez le fichier ci-dessous à la fin de votre manifeste d’extension.

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

Avertissement

L’ajout ou la modification d’étendues après la publication d’une extension n’est actuellement pas pris en charge. Si vous avez déjà chargé votre extension, supprimez-la de la Place de marché. Accédez au Visual Studio Marketplace Publishing Portal, cliquez avec le bouton droit sur votre extension et sélectionnez « Supprimer ».

Étape 3 : Effectuer l’appel de l’API REST

Il existe de nombreuses bibliothèques côté client accessibles via le KIT de développement logiciel (SDK) pour effectuer des appels d’API REST dans Azure DevOps. Ces bibliothèques sont appelées clients REST et sont des wrappers JavaScript autour des appels Ajax pour tous les points de terminaison côté serveur disponibles. Vous pouvez utiliser des méthodes fournies par ces clients au lieu d’écrire vous-même des appels Ajax. Ces méthodes mappent les réponses de l’API à des objets qui peuvent être consommés par votre code.

Dans cette étape, nous mettons à jour l’appel VSS.require pour charger TFS/WorkItemTracking/RestClient, qui fournit le client REST WorkItemTracking. Nous pouvons utiliser ce client REST pour obtenir des informations sur une requête appelée Feedback sous le dossier Shared Queries.

Dans la fonction que nous passons à VSS.register, nous créons une variable pour contenir l’ID de projet actuel. Nous avons besoin de cette variable pour extraire la requête. Nous créons également une méthode getQueryInfo pour utiliser le client REST. Cette méthode qui est ensuite appelée à partir de la méthode de chargement.

La méthode getClient fournit une instance du client REST dont nous avons besoin. La méthode getQuery retourne la requête encapsulée dans une promesse. La mise à jour VSS.require se présente comme suit :

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

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

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

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

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

Notez l’utilisation de la méthode Failure de WidgetStatusHelper. Il vous permet d’indiquer à l’infrastructure de widget qu’une erreur s’est produite et de tirer parti de l’expérience d’erreur standard fournie à tous les widgets.

Si vous n’avez pas la Feedback requête sous le Shared Queries dossier, remplacez Shared Queries\Feedback dans le code par le chemin d’accès d’une requête qui existe dans votre projet.

Étape 4 : Afficher la réponse

La dernière étape consiste à afficher les informations de requête à l’intérieur du widget. La getQuery fonction retourne un objet de type Contracts.QueryHierarchyItem à l’intérieur d’une promesse. Dans cet exemple, nous affichons l’ID de requête, le nom de la requête et le nom du créateur de la requête sous le texte « Hello World ». Remplacez le // Do something with the query commentaire par ce qui suit :

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

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

Votre finale hello-world2.html est la suivante :

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

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

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

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

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

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

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

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

Étape 5 : Mises à jour du manifeste d’extension

Dans cette étape, nous mettons à jour le manifeste d’extension pour inclure une entrée pour notre deuxième widget. Ajoutez une nouvelle contribution au tableau dans la contributions propriété et ajoutez le nouveau fichier hello-world2.html au tableau dans la propriété files. Vous avez besoin d’une autre image d’aperçu pour le deuxième widget. Nommez-le preview2.png et placez-le dans le img dossier.

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

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

Étape 6 : Empaqueter, publier et partager

Empaqueter, publier et partager votre extension. Si vous avez déjà publié l’extension, vous pouvez repackager l’extension et la mettre directement à jour sur la Place de marché.

Étape 7 : Ajouter un widget à partir du catalogue

Accédez maintenant au tableau de bord de votre équipe à l’adresse https:\//dev.azure.com/{yourOrganization}/{yourProject}. Si cette page est déjà ouverte, actualisez-la. Pointez sur le bouton Modifier en bas à droite, puis sélectionnez le bouton Ajouter. Le catalogue de widgets s’ouvre là où vous trouvez le widget que vous avez installé. Choisissez votre widget et sélectionnez le bouton « Ajouter » pour l’ajouter à votre tableau de bord.

Partie 3 : Hello World avec configuration

Dans la partie 2 de ce guide, vous avez vu comment créer un widget qui affiche les informations de requête pour une requête codée en dur. Dans cette partie, nous ajoutons la possibilité de configurer la requête à utiliser au lieu de la requête codée en dur. En mode de configuration, l’utilisateur peut voir un aperçu en direct du widget en fonction de ses modifications. Ces modifications sont enregistrées dans le widget du tableau de bord lorsque l’utilisateur sélectionne Enregistrer.

Overview dashboard live preview of the widget based on changes.

Étape 1 : HTML

Les implémentations de widgets et de configurations de widgets se ressemblent beaucoup. Les deux sont implémentés dans l’infrastructure d’extension en tant que contributions. Les deux utilisent le même fichier sdk, VSS.SDK.min.js. Les deux sont basés sur HTML, JavaScript et CSS.

Copiez le fichier html-world2.html de l’exemple précédent et renommez la copie en hello-world3.html. Ajoutez un autre fichier HTML appelé configuration.html. Votre dossier ressemble maintenant à l’exemple suivant :

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

Ajoutez le code HTML ci-dessous dans « configuration.html ». Nous ajoutons essentiellement la référence obligatoire au « VSS ». Fichier SDK.min.js et élément « select » pour la liste déroulante pour sélectionner une requête dans une liste prédéfinie.
    <!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 : JavaScript - Configuration

Utilisez JavaScript pour afficher le contenu dans la configuration du widget, comme nous l’avons fait pour le widget à l’étape 3 de la partie 1 de ce guide. Ce code JavaScript restitue le contenu, initialise le SDK VSS, mappe le code de la configuration de votre widget au nom de configuration et transmet les paramètres de configuration à l’infrastructure. Dans notre cas, vous trouverez ci-dessous le code qui charge la configuration du widget. Ouvrez le fichier configuration.html et l’élément ci-dessous <script> sur .<head>

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

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

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

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

VSS.init, VSS.requireet VSS.register jouent le même rôle que celui qu’ils ont joué pour le widget, comme décrit dans la partie 1. La seule différence est que pour les configurations de widget, la fonction passée à VSS.register doit retourner un objet qui satisfait au IWidgetConfiguration contrat.

La load propriété du IWidgetConfiguration contrat doit avoir une fonction comme valeur. Cette fonction comporte l’ensemble des étapes permettant d’afficher la configuration du widget. Dans notre cas, il s’agit de mettre à jour la valeur sélectionnée de l’élément de liste déroulante avec les paramètres existants, le cas échéant. Cette fonction est appelée lorsque l’infrastructure instancie votre widget configuration

La onSave propriété du IWidgetConfiguration contrat doit avoir une fonction comme valeur. Cette fonction est appelée par l’infrastructure lorsque l’utilisateur sélectionne Enregistrer dans le volet de configuration. Si l’entrée utilisateur est prête à être enregistrée, sérialisez-la dans une chaîne, formez l’objet custom settings et utilisez WidgetConfigurationSave.Valid() pour enregistrer l’entrée utilisateur.

Dans ce guide, nous utilisons JSON pour sérialiser l’entrée utilisateur dans une chaîne. Vous pouvez choisir n’importe quelle autre façon de sérialiser l’entrée utilisateur en chaîne. Il est accessible au widget via la propriété customSettings de l’objet WidgetSettings . Le widget doit désérialiser cela, ce qui est abordé à l’étape 4.

Étape 3 : JavaScript - Activer la préversion en direct

Pour activer la mise à jour de la préversion en direct lorsque l’utilisateur sélectionne une requête dans la liste déroulante, nous attachons un gestionnaire d’événements de modification au bouton. Ce gestionnaire informe l’infrastructure que la configuration a changé. Il transmet également le customSettings à utiliser pour mettre à jour la préversion. Pour notifier l’infrastructure, la notify méthode sur le widgetConfigurationContext doit être appelée. Il prend deux paramètres, le nom de l’événement, qui est WidgetHelpers.WidgetEvent.ConfigurationChangedans ce cas, et un EventArgs objet pour l’événement, créé à partir de avec customSettings l’aide de la méthode d’assistance WidgetEvent.Args .

Ajoutez le ci-dessous dans la fonction affectée à la load propriété .

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

Vous devez informer l’infrastructure de la modification de configuration au moins une fois afin que le bouton « Enregistrer » puisse être activé.

À la fin, votre configuration.html ressemble à 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("TFS/Dashboards/WidgetHelpers", function (WidgetHelpers) {
                    VSS.register("HelloWorldWidget.Configuration", function () {   
                        var $queryDropdown = $("#query-path-dropdown");

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

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

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

Étape 4 : JavaScript - Implémenter le rechargement dans le widget

Nous avons configuré la configuration du widget pour stocker le chemin de requête sélectionné par l’utilisateur. Nous devons maintenant mettre à jour le code dans le widget pour utiliser cette configuration stockée au lieu du code dur Shared Queries/Feedback de l’exemple précédent.

Ouvrez le fichier hello-world3.html et mettez à jour le nom du widget de HelloWorldWidget2 à HelloWorldWidget3 dans la ligne où vous appelez VSS.register. Cela permet au framework d’identifier de manière unique le widget dans l’extension.

La fonction mappée à HelloWorldWidget3 via VSS.register retourne actuellement un objet qui satisfait au IWidget contrat. Étant donné que notre widget a maintenant besoin d’une configuration, cette fonction doit être mise à jour pour retourner un objet qui satisfait au IConfigurableWidget contrat. Pour ce faire, mettez à jour l’instruction return pour inclure une propriété appelée rechargement comme ci-dessous. La valeur de cette propriété est une fonction qui appelle la getQueryInfo méthode une fois de plus. Cette méthode de rechargement est appelée par le framework chaque fois que l’entrée utilisateur change pour afficher l’aperçu en direct. Cela est également appelé lorsque la configuration est enregistrée.

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

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

Le chemin de requête codé en dur dans « getQueryInfo » doit être remplacé par le chemin de requête configuré, qui peut être extrait du paramètre « widget Paramètres » passé à la méthode. Ajoutez les informations ci-dessous au début de la méthode « getQueryInfo » et remplacez le chemin de requête codé en dur par « settings.queryPath ».
var settings = JSON.parse(widgetSettings.customSettings.data);
if (!settings || !settings.queryPath) {
    var $container = $('#query-info-container');
    $container.empty();
    $container.text("Sorry nothing to show, please configure a query path.");

    return WidgetHelpers.WidgetStatusHelper.Success();
}

À ce stade, votre widget est prêt à être affiché avec les paramètres configurés.

load Les propriétés et ont reload une fonction similaire. C’est le cas pour la plupart des widgets simples. Pour les widgets complexes, il existe certaines opérations que vous souhaitez exécuter une seule fois, quel que soit le nombre de modifications de la configuration. Ou il peut y avoir des opérations lourdes qui n’ont pas besoin d’être exécutées plus d’une fois. Ces opérations font partie de la fonction correspondant à la load propriété et non à la reload propriété .

Étape 5 : Mises à jour du manifeste d’extension

Ouvrez le vss-extension.json fichier pour inclure deux nouvelles entrées dans le tableau dans la contributions propriété . Un pour le HelloWorldWidget3 widget et l’autre pour sa configuration. Vous avez besoin d’une autre image d’aperçu pour le troisième widget. Nommez-le preview3.png et placez-le dans le img dossier. Mettez à jour le tableau dans la files propriété pour inclure les deux nouveaux fichiers HTML que nous avons ajoutés dans cet exemple.

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

Notez que la contribution pour la configuration du widget suit un modèle légèrement différent du widget lui-même. Une entrée de contribution pour la configuration du widget a :
  • ID permettant d’identifier votre contribution. Cela doit être unique au sein d’une extension.
  • Type de contribution. Pour toutes les configurations de widget, cela doit être ms.vss-dashboards-web.widget-configuration
  • Tableau des cibles auxquelles la contribution contribue. Pour toutes les configurations de widget, il n’y a qu’une seule entrée : ms.vss-dashboards-web.widget-configuration.
  • Propriétés qui contiennent un ensemble de propriétés qui inclut le nom, la description et l’URI du fichier HTML utilisé pour la configuration.

Pour prendre en charge la configuration, la contribution du widget doit également être modifiée. Le tableau de cibles pour le widget doit être mis à jour afin d’inclure l’ID de la configuration sous la forme <>publisher.<id for the extension>.id for the configuration contribution<> qui est fabrikam.vsts-extensions-myExtensions.HelloWorldWidget.Configurationdans ce cas.

Avertissement

Si l’entrée de contribution pour votre widget configurable ne cible pas la configuration à l’aide de l’éditeur et du nom d’extension appropriés, comme décrit précédemment, le bouton Configurer n’apparaît pas pour le widget.

À la fin de cette partie, le fichier manifeste doit contenir trois widgets et une configuration. Vous pouvez obtenir le manifeste complet à partir de l’exemple ici.

Étape 6 : Empaqueter, publier et partager

Si vous n’avez pas encore publié votre extension, lisez cette section pour empaqueter, publier et partager votre extension. Si vous avez déjà publié l’extension avant ce point, vous pouvez repackager l’extension et la mettre directement à jour sur la Place de marché.

Étape 7 : Ajouter un widget à partir du catalogue

À présent, accédez au tableau de bord de votre équipe à l’adresse https://dev.azure.com/{yourOrganization}/{yourProject}. Si cette page est déjà ouverte, actualisez-la. Pointez sur le bouton Modifier en bas à droite, puis sélectionnez le bouton Ajouter. Cela doit ouvrir le catalogue de widgets dans lequel vous trouvez le widget que vous avez installé. Choisissez votre widget et sélectionnez le bouton « Ajouter » pour l’ajouter à votre tableau de bord.

Un message vous demandant de configurer le widget s’affiche.

Overview dashboard with a sample widget from the catalog.

Il existe deux façons de configurer des widgets. L’une consiste à pointer sur le widget, à sélectionner les points de suspension qui s’affichent en haut à droite, puis à Configurer. L’autre consiste à sélectionner le bouton Modifier en bas à droite du tableau de bord, puis à sélectionner le bouton Configurer qui apparaît en haut à droite du widget. Ouvre l’expérience de configuration sur le côté droit et un aperçu de votre widget au centre. Choisissez une requête dans la liste déroulante. L’aperçu en direct affiche les résultats mis à jour. Sélectionnez « Enregistrer » et votre widget affiche les résultats mis à jour.

Étape 8 : Configurer plus (facultatif)

Vous pouvez ajouter autant d’éléments de formulaire HTML que nécessaire dans le configuration.html pour une configuration supplémentaire. Il existe deux fonctionnalités configurables prêtes à l’emploi : le nom du widget et la taille du widget.

Par défaut, le nom que vous fournissez pour votre widget dans le manifeste d’extension est stocké en tant que nom de widget pour chaque instance de votre widget qui est ajoutée à un tableau de bord. Vous pouvez autoriser les utilisateurs à configurer cela, afin qu’ils puissent ajouter le nom qu’ils souhaitent à leur instance de votre widget. Pour autoriser une telle configuration, ajoutez isNameConfigurable:true dans la section propriétés de votre widget dans le manifeste de l’extension.

Si vous fournissez plusieurs entrées pour votre widget dans le supportedSizes tableau du manifeste d’extension, les utilisateurs peuvent également configurer la taille du widget.

Le manifeste d’extension du troisième exemple de ce guide ressemblerait à ce qui suit si nous activons la configuration du nom et de la taille du widget :

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

Avec la modification précédente, repackagez et mettez à jour votre extension. Actualisez le tableau de bord contenant ce widget (Hello World Widget 3 (avec configuration)). Ouvrez le mode de configuration de votre widget. Vous devriez maintenant être en mesure de voir l’option permettant de modifier le nom et la taille du widget.

Widget where name and size can be configured

Continuez et choisissez une autre taille dans la liste déroulante. Vous voyez que l’aperçu en direct est redimensionné. Enregistrez la modification et le widget du tableau de bord est également redimensionné.

Avertissement

Si vous supprimez une taille déjà prise en charge, le widget ne parvient pas à se charger correctement. Nous travaillons sur un correctif pour une version ultérieure.

La modification du nom du widget n’entraîne aucune modification visible du widget. Cela est dû au fait que nos exemples de widgets n’affichent le nom du widget nulle part. Nous allons modifier l’exemple de code pour afficher le nom du widget au lieu du texte codé en dur « Hello World ».

Pour ce faire, remplacez le texte codé en dur « Hello World » par widgetSettings.name dans la ligne où nous définissons le texte de l’élémenth2. Cela garantit que le nom du widget s’affiche chaque fois que le widget est chargé lors de l’actualisation de la page. Étant donné que nous voulons que la préversion en direct soit mise à jour chaque fois que la configuration change, nous devons également ajouter le même code dans la reload partie de notre code. L’instruction return finale dans hello-world3.html est la suivante :

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

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

        return getQueryInfo(widgetSettings);
    }
}

Repackager et mettre à jour à nouveau votre extension. Actualisez le tableau de bord contenant ce widget. Toute modification apportée au nom du widget, en mode de configuration, met à jour le titre du widget maintenant.