Partager 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, chacun construit sur le précédent. Vous commencez par un widget simple et vous terminez 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

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

  • Une « organisation » dans Azure DevOps. Créez une organisation.

  • Éditeur de texte. Pour la plupart des tutoriels, nous utilisons Visual Studio Code.

  • La dernière version de Node.js.

  • CLI multiplateforme pour Azure DevOps (tfx-cli) pour empaqueter vos extensions.

    • tfx-cli peut être installé à l’aide npmd’un composant de Node.js en exécutant npm i -g tfx-cli
  • Répertoire de base de votre projet. Ce répertoire, appelé home tout au long du didacticiel, doit avoir la structure suivante après que vous ayez effectué les étapes décrites dans cet article :

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

Remarque

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

Commencez par découvrir quelques styles de base pour les widgets que nous fournissons prêts à l'emploi, ainsi que des 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

Le script du Kit de développement logiciel (SDK) principal, VSS.SDK.min.jspermet 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.

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

npm install vss-web-extension-sdk

Recherchez le fichier sdk VSS.SDK.min.js client situé dans le vss-web-extension-sdk/lib dossier, puis placez-le dans votre home/sdk/scripts dossier.

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

Étape 2 : Configurer votre page HTML

Création d’un fichier appelé hello-world.html. Votre page HTML est le ciment qui maintient votre mise en page ensemble et inclut des références à CSS et JavaScript. Vous pouvez nommer ce fichier n’importe quoi. Si vous utilisez un autre nom de fichier, mettez à jour toutes les références avec hello-world le nom que vous utilisez.

Votre widget est basé sur HTML et est hébergé dans un iframe. Copiez le code HTML suivant dans votre hello-world.html fichier. 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 vous utilisez 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 élément <script> 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 ce cas, le code suivant imprime Hello World dans le widget. Ajoutez cet script élément dans le head de l'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 liaison entre l'iframe hébergeant le widget et la trame hôte.

  • Transmettez explicitNotifyLoaded: true afin que le widget puisse avertir explicitement l’hôte lorsqu’il a 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. Utilisez usePlatformStyles: true ce qui permet au widget d’utiliser des styles principaux Azure DevOps pour les éléments HTML, tels que body et div. Si vous ne souhaitez pas que le widget utilise ces styles, passez 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. Transmettez donc le nom du module correspondant TFS/Dashboards/WidgetHelpers et une fonction de 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, appelez VSS.notifyLoadSucceeded pour notifier l’achèvement du chargement.

  • WidgetHelpers.IncludeWidgetStyles inclut une feuille de style avec un 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 au id qui identifie votre contribution, comme décrit dans l'étape 5. Pour les widgets, la fonction transmise VSS.register doit retourner 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 afficher 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 comme une 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.

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 x 98 pixels. Choisissez une image, nommez-la logo.pnget placez-la dans le img dossier. Vous pouvez nommer l'image comme vous le souhaitez, à condition que le manifeste d'extension de l'étape suivante soit mis à jour avec le nom que vous utilisez.

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

Chaque extension doit avoir un fichier manifeste d’extension. Créez un fichier json appelé vss-extension.json 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
        }
    ]
}

Le vss-extension.json fichier doit toujours se trouver à la racine du répertoire de base. 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 manifeste vss-extension.json.

Pour plus d'informations sur les attributs requis, veuillez consulter la référence du manifeste d'extension.

Remarque

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

Icônes

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

Contributions

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

  • Pour identifier votre contribution, utilisez id. Cet ID doit être unique au sein d’une extension. Cet ID doit correspondre au nom que vous avez utilisé à "l'étape 3" pour enregistrer votre widget.

  • The type de contribution. Pour tous les widgets, le type doit être ms.vss-dashboards-web.widget.

  • Le tableau targets auquel la contribution contribue. Pour tous les widgets, la cible doit être [ms.vss-dashboards-web.widget-catalog].

  • properties 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é Descriptif
    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 qu'elle s'affiche dans le catalogue de widgets. L’image doit être de 98 x 98 pixels. 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 afin de l'afficher dans le catalogue de widgets. L’image doit être de 330 x 160 pixels. 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é dans 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 supplément de 10 px qui représentent la marge 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.

Pour en savoir plus sur les points de contribution, consultez les points d’extensibilité.

Fichiers

La files sanza indique les fichiers que vous souhaitez inclure dans votre package : votre page HTML, vos scripts, le script du Kit de développement logiciel (SDK) et votre logo. Définissez addressable sur true sauf si vous incluez d’autres fichiers qui n’ont pas besoin d’être adressables par URL.

Remarque

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 en tant que fichiers .vsix compatibles VSIX 2.0. Microsoft fournit une interface de ligne de commande multiplateforme (CLI) pour empaqueter votre extension.

Obtenir l’outil d’empaquetage

Vous pouvez installer ou mettre à jour tfx-cli à l’aide npmd’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 utilisez l'option de ligne de commande --rev-version. 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 visual Studio Marketplace.

  2. Si vous n’êtes pas déjà membre d’un éditeur existant, vous devez créer un éditeur. Si vous avez déjà un éditeur, veuillez faire défiler jusqu'à Publier des extensions sous Sites connexes et sélectionner cette option.

    • Spécifiez un identificateur pour votre éditeur, par exemple : mycompany-myteam.
      • L’identificateur est utilisé comme valeur pour l’attribut publisher dans le fichier manifeste de vos extensions.
    • Spécifiez un nom complet pour votre éditeur, par exemple : Mon équipe.
  3. Consultez le Contrat d’éditeur du Marketplace, 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 ensemble unique d’informations d’identification entre plusieurs utilisateurs, ce qui améliore la sécurité.

Mettez à jour le vss-extension.json fichier manifeste dans les exemples pour remplacer l’ID d’éditeur factice fabrikam 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 téléverser votre extension via la ligne de commande en utilisant la commande tfx extension publish au lieu de tfx extension create pour 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. Veuillez sélectionner Aperçu des >tableaux de bord.

  3. Sélectionnez Ajouter un widget.

  4. Sélectionnez votre widget, puis choisissez 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
|--- SDK
    |--- 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

Pour conserver les informations relatives à la requête, veuillez ajouter un nouvel élément div sous l'élément 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 scopes doivent être spécifiés dans le manifeste de l’extension. Nous ajoutons le champ 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. Pour afficher toutes les étendues disponibles, consultez Étendues.

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 : Appeler 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, veuillez mettre à jour l'appel VSS.require pour charger AzureDevOps/WorkItemTracking/RestClient, qui fournit le client REST WorkItemTracking. Vous pouvez utiliser ce client REST pour obtenir des informations sur une requête appelée Feedback sous le dossier Shared Queries.

À l'intérieur de la fonction que vous passez à VSS.register, créez une variable pour contenir l'ID du projet actuel. Vous avez besoin de cette variable pour extraire la requête. Créez également une méthode getQueryInfo pour utiliser le client REST. Cette méthode est ensuite appelée à partir de la méthode de chargement.

La méthode getClient fournit une instance du client REST dont vous avez 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 dossier Shared Queries, 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 fonction getQuery retourne un objet de type Contracts.QueryHierarchyItem à l'intérieur d'une promesse. Dans cet exemple, vous affichez l’ID de requête, le nom de la requête et le nom du créateur de 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 hello-world2.html final ressemble à 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 propriété , et ajoutez le nouveau fichier au tableau dans la propriété . Vous avez besoin d’une autre image d’aperçu pour le deuxième widget. Nommez ceci preview2.png et placez-le dans le dossier img.

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

Créez un package, publiez et partagez 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 à code fixe. Dans cette partie, vous ajoutez 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 en direct du tableau de bord Aperçu du widget basé sur les modifications.

Étape 1 : Ajouter un fichier HTML

Les implémentations de widgets et de configurations de widgets sont beaucoup plus similaires. 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 nommé 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. Vous ajoutez 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.

Veuillez ouvrir le fichier configuration.html et l'élément suivant <script> dans le fichier <head>.

<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.require et VSS.register jouent le même rôle que celui qu'ils jouaient 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 répond 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-la 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 de caractères. Il est accessible au widget via la propriété customSettings de l’objet WidgetSettings. Le widget doit être désérialisé, ce qui est décrit à l'étape 4.

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

Pour activer la mise à jour en préversion en direct lorsque l’utilisateur sélectionne une requête dans la liste déroulante, attachez 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 le cadre, la méthode notify 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 attribuée à la propriété load.

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

Vérifiez que l’infrastructure est avertie de la modification de configuration au moins une fois pour activer le bouton Enregistrer .

À la fin, votre configuration.html 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

Configurez la configuration du widget pour stocker le chemin de requête sélectionné par l’utilisateur. Vous devez maintenant mettre à jour le code dans le widget pour utiliser cette configuration stockée au lieu du code codé en 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 le contrat IWidget. Étant donné que notre widget a maintenant besoin d’une configuration, cette fonction doit être mise à jour afin de retourner un objet qui satisfait le 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 intégré 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 tout 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.

Les propriétés load et reload ont 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, peu importe le nombre de fois où la configuration change. 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é : l’une 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 ceci preview3.png et placez-le dans le dossier img. Mettez à jour le tableau dans la files propriété pour inclure les deux nouveaux fichiers HTML que vous avez 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 :

  • Pour identifier votre contribution, utilisez id. L’ID doit être unique dans une extension.
  • The type de contribution. Pour toutes les configurations de widget, il doit être ms.vss-dashboards-web.widget-configuration.
  • Le tableau targets auquel la contribution contribue. Pour toutes les configurations de widget, il a une entrée unique : ms.vss-dashboards-web.widget-configuration.
  • properties Qui contient un ensemble de propriétés incluant le nom, la description et l’URI du fichier HTML utilisé pour la configuration.

Afin de permettre la configuration, il est également nécessaire de modifier la contribution du widget. Le tableau du targets widget doit être mis à jour pour inclure l’ID de la configuration sous la forme suivante :

<publisher>.<id for the extension>.<id for the configuration contribution>

Dans ce cas, l’exemple est :

fabrikam.vsts-extensions-myExtensions.HelloWorldWidget.Configuration

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. Pour obtenir l’exemple de fichier manifeste complet, consultez vss-extension.json.

Étape 6 : Empaqueter, publier et partager

Si votre extension n’est pas publiée, consultez l’étape 6 : Package, publication et partage. 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. 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’un consiste à passer la souris sur le widget, à sélectionner les trois points qui s’affichent dans le coin supérieur droit, puis à sélectionner 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 des configurations supplémentaires. 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 vous activez le nom et la configuration de taille du widget :

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

Repackager et mettre à 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 où le nom et la taille du widget 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ù.

Modifiez l’exemple de code pour afficher le nom du widget au lieu du texte codé en dur Hello World en remplaçant le texte codé en dur Hello WorldwidgetSettings.name par la ligne dans laquelle vous définissez 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 vous souhaitez que l’aperçu en direct soit mis à jour chaque fois que la configuration change, vous devez également ajouter le même code dans la reload partie de votre 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);
    }
}

Veuillez reconditionner et mettre à jour 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.