Partage via


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

  • Connaissances: Certaines connaissances en JavaScript, HTML et CSS sont requises pour le développement de widgets.
  • Une organisation dans Azure DevOps.
  • Éditeur de texte. Pour la plupart des didacticiels, nous utilisons Visual Studio Code.
  • Dernière version du nœud.
  • 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

Dans ce didacticiel

  1. Partie 1 : Montre comment créer un widget, qui imprime un message « Hello World » simple.
  2. Partie 2 : S’appuie sur la première partie en ajoutant un appel à une API REST Azure DevOps.
  3. Partie 3 : Explique comment ajouter une configuration à votre widget.

Remarque

Si vous êtes pressé et que vous souhaitez mettre la main sur le code immédiatement, vous pouvez télécharger les exemples. 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.

Prise en main de certains styles de base pour les widgets que nous fournissons prêtes à l’emploi et quelques conseils sur la structure des widgets .

Partie 1 : Hello World

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

Capture d’écran du tableau de bord Vue d’ensemble avec un exemple de 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.

Pour récupérer le Kit de développement logiciel (SDK), utilisez la commande « npm install » :

npm install vss-web-extension-sdk

Pour plus d’informations, consultez la page GitHub du Kit de développement logiciel (SDK) client.

Étape 2 : Configurer 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. Mettez à 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 suivant dans hello-world.html. Nous ajoutons la référence obligatoire au VSS.SDK.min.js fichier et incluons un h2 élément, 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 : Mettre à jour 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, le code suivant imprime « 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 transmettons explicitNotifyLoaded: true afin que le widget puisse avertir explicitement l’hôte lorsqu’il est chargé. 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 transmettons usePlatformStyles: true afin que le widget puisse utiliser des styles principaux Azure DevOps pour les éléments HTML (tels que le corps, la div, etc.). 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. Pour utiliser ces styles, encapsulez votre contenu à l’intérieur d’un élément HTML avec la classe widget.
  • 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 ». Il s’agit de cette fonction 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.

  • vss-extension.json doit toujours être à 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 : Mettre à jour votre logo d’extension : logo.png

Votre logo s’affiche dans la Place de marché et dans le catalogue de widgets une fois qu’un utilisateur installe 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.

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 : Créer votre manifeste d’extension : vss-extension.json

Chaque extension doit avoir un fichier manifeste d’extension.

  • Lisez la référence du manifeste d’extension.
  • En savoir plus sur les points de contribution dans les points d’extensibilité.
  • Créez un fichier json (vss-extension.jsonpar exemple) dans le home répertoire avec le contenu suivant :
{
    "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
        }
    ]
}

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

Remarque

Remplacez l’éditeur par le nom de votre éditeur. Pour créer un éditeur, consultez Package/Publish/Install.

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 structure de dossiers différente ou un autre nom de fichier, spécifiez le chemin d’accès relatif approprié ici.
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. L’image doit être de 330 px x 160 px. Si vous avez utilisé une structure de dossiers différente ou un autre nom de fichier, spécifiez le chemin d’accès relatif approprié ici.
URI Chemin relatif du fichier HTML que vous avez ajouté à l’étape 1. Si vous avez utilisé une structure de dossiers différente ou un autre nom de fichier, spécifiez le chemin d’accès relatif approprié ici.
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 un px supplémentaire de 10 px qui représentent la coupe 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 Actuellement, seuls les tableaux de bord d’équipe sont pris en charge. La valeur doit être project_team. Les futures mises à jour peuvent inclure d’autres options pour les étendues du tableau de bord.

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 votre extension écrite, 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

Remarque

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

Après avoir empaqueter votre extension 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, créez-en 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 devez créer un éditeur. Si vous disposez déjà d’un éditeur, faites défiler vers et sélectionnez Publier des 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é, puis 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.

La publication d’extensions sous un éditeur commun simplifie le processus pour les équipes et les organisations, offrant une approche plus sécurisée. Cette méthode élimine la nécessité de distribuer un seul ensemble d’informations d’identification entre plusieurs utilisateurs, d’améliorer la sécurité et

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

Vous pouvez maintenant charger votre extension sur la Place de marché.

Sélectionnez 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 avez é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. Connectez-vous à votre projet. http://dev.azure.com/{Your_Organization}/{Your_Project}

  2. Sélectionnez Tableaux de bord vue d’ensemble>.

  3. Sélectionnez 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 l’exemple suivant, nous utilisons l’API REST pour WorkItemTracking pour extraire des informations sur une requête existante et afficher des informations de requête dans le widget 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 : Ajouter un fichier 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 à l’exemple suivant :

|--- README.md |--- node_modules
Kit de développement logiciel (SDK) |---
Scripts |--- |--- VSS. SDK.min.js des scripts |--- img |--- logo.png |---
|--- hello-world.html // page html à utiliser pour votre widget |--- hello-world2.html // copie renommée de hello-world.html |--- vss-extension.json // le manifeste de l’extension

Pour contenir les informations de requête, ajoutez un nouvel div élément sous le h2. Mettez à jour le nom du widget de HelloWorldWidget à HelloWorldWidget2 dans la ligne où vous appelez VSS.register. Cette action permet au framework d’identifier de manière unique le widget dans 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 code suivant à la fin de votre manifeste d’extension.

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

Pour inclure d’autres propriétés, vous devez les répertorier explicitement, par exemple :

{
    "name": "example-widget",
    "publisher": "example-publisher",
    "version": "1.0.0",
    "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 portail de publication visual Studio Marketplace, sélectionnez avec le bouton droit votre extension, puis 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 d’API aux objets que votre code peut consommer.

Dans cette étape, nous mettons à jour l’appel VSS.require pour charger AzureDevOps/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(["AzureDevOps/Dashboards/WidgetHelpers", "AzureDevOps/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 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 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 commentaire // Do something with the query par le code suivant :

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

Votre finale hello-world2.html est semblable à l’exemple suivant :

<!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, 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($('<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

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/{Your_Organization}/{Your_Project}. Si cette page est déjà ouverte, actualisez-la. Pointez sur Modifier et sélectionnez Ajouter. Le catalogue de widgets s’ouvre là où vous trouvez le widget que vous avez installé. Pour l’ajouter à votre tableau de bord, choisissez votre widget, puis sélectionnez Ajouter.

Partie 3 : Configurer Hello World

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.

Capture d’écran de la préversion dynamique du tableau de bord Vue d’ensemble du widget en fonction des modifications.

Étape 1 : Ajouter un fichier 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 suivant dans configuration.html. Nous ajoutons essentiellement la référence obligatoire au VSS.SDK.min.js fichier et un select élément 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 : Configurer JavaScript

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, le code suivant charge la configuration du widget. Ouvrez le fichier configuration.html et l’élément suivant <script> dans le <head>fichier .

<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>
  • VSS.init, VSS.requireet VSS.register jouez le même rôle 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, qui est couvert à 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 avertit 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 code suivant 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);
 });

Révisé : vérifiez que l’infrastructure est avertie du changement de configuration au moins une fois pour activer le bouton Enregistrer .

À la fin, votre configuration.html apparence ressemble à l’exemple suivant :

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

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

Nous configurons la configuration du widget pour stocker le chemin d’accès 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. Cette action 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 conformément au code suivant. 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. Cette méthode de rechargement est également appelée 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 widgetSettings passé à la méthode . Ajoutez le code suivant au début de la getQueryInfo méthode 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 : Mettre à jour le 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.azuredevops-extensions-myExtensions.HelloWorldWidget.Configuration"
             ],
             "properties": {
                 "name": "Hello World Widget 3 (with config)",
                 "description": "My third widget",
                 "previewImageUrl": "img/preview3.png",                       
                 "uri": "hello-world3.html",
                 "supportedSizes": [
                      {
                             "rowSpan": 1,
                             "columnSpan": 2
                         }
                     ],
                 "supportedScopes": ["project_team"]
             }
         },
         {
             "id": "HelloWorldWidget.Configuration",
             "type": "ms.vss-dashboards-web.widget-configuration",
             "targets": [ "ms.vss-dashboards-web.widget-configuration" ],
             "properties": {
                 "name": "HelloWorldWidget Configuration",
                 "description": "Configures HelloWorldWidget",
                 "uri": "configuration.html"
             }
         }
    ],
    "files": [
            {
                "path": "hello-world.html", "addressable": true
            },
             {
                "path": "hello-world2.html", "addressable": true
            },
            {
                "path": "hello-world3.html", "addressable": true
            },
            {
                "path": "configuration.html", "addressable": true
            },
            {
                "path": "sdk/scripts", "addressable": true
            },
            {
                "path": "img", "addressable": true
            }
        ],
        ...     
}

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 les éléments suivants :

  • ID permettant d’identifier votre contribution. L’ID doit être unique dans une extension.
  • Type de contribution. Pour toutes les configurations de widget, il doit être ms.vss-dashboards-web.widget-configuration
  • Tableau des cibles auxquelles la contribution contribue. Pour toutes les configurations de widget, elle a une entrée unique : 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 votre extension n’est pas publiée, consultez cette section. 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

À présent, accédez au tableau de bord de votre équipe à l’adresse https://dev.azure.com/{Your_Organization}/{Your_Project}. Si cette page est déjà ouverte, actualisez-la. Pointez sur Modifier et sélectionnez Ajouter. Cette action doit ouvrir le catalogue de widgets où vous trouvez le widget que vous avez installé. Pour ajouter le widget à votre tableau de bord, choisissez votre widget, puis sélectionnez Ajouter.

Un message similaire à ce qui suit vous demande de configurer le widget.

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

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 la configuration.html configuration supplémentaire. Il existe deux fonctionnalités configurables disponibles 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 afin qu’ils puissent ajouter n’importe quel 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 pour le troisième exemple de ce guide ressemble à l’exemple suivant si nous activez le nom du widget et la configuration de taille :

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

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.

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

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

La modification du nom du widget n’entraîne aucune modification visible dans le widget, car nos exemples de widgets n’affichent pas le nom du widget n’importe où. Modifions 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 » widgetSettings.name par la ligne dans laquelle nous définissons le texte de l’élément h2 . Cette action 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.