Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Azure DevOps Services | Azure DevOps Server | Azure DevOps Server 2022
Les widgets sont implémentés sous forme de contributions dans l’infrastructure d’extension. Une seule extension peut inclure plusieurs contributions de widget. Cet article explique comment créer une extension qui fournit un ou plusieurs widgets.
Conseil
Consultez notre documentation la plus récente sur le développement d’extensions à l’aide du Kit de développement logiciel (SDK) d’extension Azure DevOps.
Conseil
Si vous démarrez une nouvelle extension Azure DevOps, essayez d'abord ces collections d'exemples maintenues : elles fonctionnent avec les builds de produit actuels et couvrent les scénarios modernes (par exemple, en ajoutant des onglets sur les pages de requête de tirage).
- Exemple d’extension Azure DevOps (GitHub) : exemple de démarrage compact qui illustre les modèles d’extension courants : https://github.com/microsoft/azure-devops-extension-sample
- Exemples d’extensions Azure DevOps (collection héritée et guide des contributions) : installez pour examiner les cibles de l'interface utilisateur, ou consultez la source : https://marketplace.visualstudio.com/items/ms-samples.samples-contributions-guidehttps://github.com/Microsoft/vso-extension-samples/tree/master/contributions-guide
- Exemples Microsoft Learn (parcourir des exemples Azure DevOps) : organisés, des exemples à jour dans les documents Microsoft : /samples/browse/?terms=azure%20devops%20extension
Si un exemple ne fonctionne pas dans votre organisation, installez-le dans une organisation personnelle ou de test et comparez les ID cibles et les versions d’API du manifeste d’extension avec les documents actuels. Pour obtenir des informations de référence et des API, consultez :
Prérequis
| Besoin | Descriptif |
|---|---|
| Connaissances en programmation | Connaissances JavaScript, HTML et CSS pour le développement de widgets |
| Organisation Azure DevOps | Créez une organisation. |
| Éditeur de texte | Nous utilisons Visual Studio Code pour les didacticiels |
| Node.JS | Dernière version de Node.js |
| Interface CLI multiplateforme |
tfx-cli pour empaqueter des extensions Installer à l’aide de : npm i -g tfx-cli |
| Répertoire du projet | Répertoire de base avec cette structure après avoir suivi le tutoriel :|--- README.md|--- sdk |--- node_modules |--- scripts |--- VSS.SDK.min.js|--- img |--- logo.png|--- scripts|--- hello-world.html // html page for your widget|--- vss-extension.json // extension manifest |
Vue d’ensemble du didacticiel
Ce tutoriel explique le développement de widgets par trois exemples progressifs :
| Partie | Focus | Contenu |
|---|---|---|
| Partie 1 : Hello World | Création de widget de base | Créer un widget qui affiche du texte |
| Partie 2 : Intégration de l’API REST | Appels d’API Azure DevOps | Ajouter des fonctionnalités d’API REST pour extraire et afficher des données |
| Partie 3 : Configuration du widget | Personnalisation utilisateur | Implémenter des options de configuration pour votre widget |
Conseil
Si vous préférez passer directement aux exemples de travail, les exemples inclus (voir la note précédente) affichent un ensemble de widgets que vous pouvez empaqueter et publier.
Avant de commencer, passez en revue les styles de widget de base et les conseils structurels que nous fournissons.
Partie 1 : Hello World
Créez un widget de base qui affiche « Hello World » à l’aide de JavaScript. Cette base illustre les principaux concepts de développement de widgets.
Étape 1 : Installer le Kit de développement logiciel (SDK) client
Le Kit de développement logiciel (SDK) VSS permet à votre widget de communiquer avec Azure DevOps. Installez-le à l’aide de npm :
npm install vss-web-extension-sdk
Copiez le fichier VSS.SDK.min.js de vss-web-extension-sdk/lib vers votre dossier home/sdk/scripts.
Pour plus de documentation sur le SDK, consultez la page GitHub du Kit de développement logiciel (SDK) client.
Étape 2 : Créer la structure HTML
Créez hello-world.html dans votre répertoire de projet. Ce fichier fournit la disposition et les références du widget aux scripts requis.
<!DOCTYPE html>
<html>
<head>
<script src="sdk/scripts/VSS.SDK.min.js"></script>
</head>
<body>
<div class="widget">
<h2 class="title"></h2>
</div>
</body>
</html>
Les widgets s’exécutent dans des iframes, donc la plupart des éléments principaux HTML sauf <script> et <link> sont ignorés par l’infrastructure.
Étape 3 : Ajouter un widget JavaScript
Pour implémenter la fonctionnalité de widget, ajoutez ce script à la <head> section de votre fichier HTML :
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers"], function (WidgetHelpers) {
WidgetHelpers.IncludeWidgetStyles();
VSS.register("HelloWorldWidget", function () {
return {
load: function (widgetSettings) {
var $title = $('h2.title');
$title.text('Hello World');
return WidgetHelpers.WidgetStatusHelper.Success();
}
};
});
VSS.notifyLoadSucceeded();
});
</script>
Composants JavaScript clés
| Fonction | Objectif |
|---|---|
VSS.init() |
Initialise la communication entre le widget et Azure DevOps |
VSS.require() |
Charge les bibliothèques de SDK requises et les widgets d’assistance |
VSS.register() |
Inscrit votre widget avec un identificateur unique |
WidgetHelpers.IncludeWidgetStyles() |
Applique le style Azure DevOps par défaut |
VSS.notifyLoadSucceeded() |
Notifie le framework que le chargement a été effectué avec succès |
Importante
Le nom du widget dans VSS.register() doit correspondre au id le manifeste de votre extension (étape 5).
Étape 4 : Ajouter des images d’extension
Créez les images requises pour votre extension :
-
Logo d’extension : image de 98 x 98 pixels nommée
logo.pngdans leimgdossier -
Icône de catalogue de widgets : image de 98 x 98 pixels nommée
CatalogIcon.pngdans leimgdossier -
Aperçu du widget : image de 330 x 160 pixels nommée
preview.pngdans leimgdossier
Ces images s’affichent dans la Place de marché et le catalogue de widgets lorsque les utilisateurs parcourent les extensions disponibles.
Étape 5 : Créer le manifeste d’extension
Créez vss-extension.json dans le répertoire racine de votre projet. Ce fichier définit les métadonnées et les contributions de votre extension :
{
"manifestVersion": 1,
"id": "azure-devops-extensions-myExtensions",
"version": "1.0.0",
"name": "My First Set of Widgets",
"description": "Samples containing different widgets extending dashboards",
"publisher": "fabrikam",
"categories": ["Azure Boards"],
"targets": [
{
"id": "Microsoft.VisualStudio.Services"
}
],
"icons": {
"default": "img/logo.png"
},
"contributions": [
{
"id": "HelloWorldWidget",
"type": "ms.vss-dashboards-web.widget",
"targets": [
"ms.vss-dashboards-web.widget-catalog"
],
"properties": {
"name": "Hello World Widget",
"description": "My first widget",
"catalogIconUrl": "img/CatalogIcon.png",
"previewImageUrl": "img/preview.png",
"uri": "hello-world.html",
"supportedSizes": [
{
"rowSpan": 1,
"columnSpan": 2
}
],
"supportedScopes": ["project_team"]
}
}
],
"files": [
{
"path": "hello-world.html",
"addressable": true
},
{
"path": "sdk/scripts",
"addressable": true
},
{
"path": "img",
"addressable": true
}
]
}
Importante
Remplacez par "publisher": "fabrikam" votre nom d’éditeur réel. Découvrez comment créer un éditeur.
Propriétés de manifeste essentielles
| Section | Objectif |
|---|---|
| Informations de base | Nom de l’extension, version, description et éditeur |
| icônes | Chemins d’accès aux ressources visuelles de votre extension |
| Contributions | Définitions de widgets, notamment l’ID, le type et les propriétés |
| Fichiers | Tous les fichiers à inclure dans le package d’extension |
Pour obtenir la documentation complète du manifeste, consultez la référence du manifeste d’extension.
Étape 6 : Empaqueter et publier votre extension
Empaqueter votre extension et la publier sur Visual Studio Marketplace.
Installer l’outil d’empaquetage
npm i -g tfx-cli
Créer votre package d’extension
À partir de votre répertoire de projet, exécutez :
tfx extension create --manifest-globs vss-extension.json
Cette action crée un .vsix fichier qui contient votre extension empaquetée.
Configurer un éditeur
- Accédez au portail de publication Visual Studio Marketplace.
- Connectez-vous et créez un éditeur si vous n’en avez pas.
- Choisissez un identificateur d’éditeur unique (utilisé dans votre fichier manifeste).
- Mettez à jour votre
vss-extension.jsonpour utiliser votre nom d'éditeur au lieu de « fabrikam ».
Charger votre extension
- Dans le portail de publication, sélectionnez Charger une nouvelle extension.
- Choisissez votre
.vsixfichier et chargez-le. - Partagez l’extension avec votre organisation Azure DevOps.
Vous pouvez également utiliser la ligne de commande :
tfx extension publish --manifest-globs vss-extension.json --share-with yourOrganization
Conseil
Permet --rev-version d’incrémenter automatiquement le numéro de version lors de la mise à jour d’une extension existante.
Étape 7 : Installer et tester votre widget
Pour tester, ajoutez votre widget à un tableau de bord :
- Accédez à votre projet Azure DevOps :
https://dev.azure.com/{Your_Organization}/{Your_Project}. - Rendez-vous dans Vue d’ensemble>Tableaux de bord.
- Sélectionnez Ajouter un widget.
- Recherchez votre widget dans le catalogue, puis sélectionnez Ajouter.
Votre widget « Hello World » s’affiche sur le tableau de bord, affichant le texte que vous avez configuré.
Étape suivante : Passez à la partie 2 pour apprendre à intégrer des API REST Azure DevOps dans votre widget.
Partie 2 : Hello World avec l’API REST Azure DevOps
Étendez votre widget pour interagir avec les données Azure DevOps à l’aide d’API REST. Cet exemple montre comment extraire des informations de requête et l’afficher dynamiquement dans votre widget.
Dans cette partie, utilisez l’API REST Suivi des éléments de travail pour récupérer des informations sur une requête existante et afficher les détails de la requête sous le texte « Hello World ».
Étape 1 : Créer le fichier HTML amélioré
Créez un fichier de widget qui s’appuie sur l’exemple précédent. Copiez et renommez-le hello-world.html en hello-world2.html. Votre structure de projet inclut désormais les éléments suivants :
|--- README.md
|--- node_modules
|--- sdk/
|--- scripts/
|--- VSS.SDK.min.js
|--- img/
|--- logo.png
|--- scripts/
|--- hello-world.html // Part 1 widget
|--- hello-world2.html // Part 2 widget (new)
|--- vss-extension.json // Extension manifest
Mettre à jour la structure HTML du widget
Apportez ces modifications à hello-world2.html:
-
Ajoutez un conteneur pour les données de requête : incluez un nouvel
<div>élément pour afficher les informations de requête. -
Mettez à jour l’identificateur du widget : changez le nom du widget
HelloWorldWidgetparHelloWorldWidget2pour une identification unique.
<!DOCTYPE html>
<html>
<head>
<script src="sdk/scripts/VSS.SDK.min.js"></script>
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers"], function (WidgetHelpers) {
WidgetHelpers.IncludeWidgetStyles();
VSS.register("HelloWorldWidget2", function () {
return {
load: function (widgetSettings) {
var $title = $('h2.title');
$title.text('Hello World');
return WidgetHelpers.WidgetStatusHelper.Success();
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
</head>
<body>
<div class="widget">
<h2 class="title"></h2>
<div id="query-info-container"></div>
</div>
</body>
</html>
Étape 2 : Configurer les autorisations d’accès aux API
Avant d’effectuer des appels d’API REST, configurez les autorisations requises dans votre manifeste d’extension.
Ajouter l’étendue de travail
L’étendue vso.work accorde un accès en lecture seule aux éléments de travail et aux requêtes. Ajoutez cette étendue à votre vss-extension.json :
{
"scopes": [
"vso.work"
]
}
Exemple de manifeste complet
Pour obtenir un manifeste complet avec d’autres propriétés, structurez-le comme suit :
{
"name": "example-widget",
"publisher": "example-publisher",
"version": "1.0.0",
"scopes": [
"vso.work"
]
}
Importante
Limitations de l’étendue : l’ajout ou la modification d’étendues après la publication n’est pas prise en charge. Si vous avez déjà publié votre extension, vous devez d’abord la supprimer de la Place de marché. Accédez au portail de publication visual Studio Marketplace, recherchez votre extension, puis sélectionnez Supprimer.
Étape 3 : Implémenter l’intégration de l’API REST
Azure DevOps fournit des bibliothèques de client REST JavaScript via le Kit de développement logiciel (SDK). Ces bibliothèques encapsulent les appels AJAX et mappent les réponses d’API aux objets utilisables.
Mettre à jour le widget JavaScript
Remplacez l'appel VSS.require dans votre hello-world2.html pour inclure le client REST de suivi des éléments de travail :
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers", "AzureDevOps/WorkItemTracking/RestClient"],
function (WidgetHelpers, WorkItemTrackingRestClient) {
WidgetHelpers.IncludeWidgetStyles();
VSS.register("HelloWorldWidget2", function () {
var projectId = VSS.getWebContext().project.id;
var getQueryInfo = function (widgetSettings) {
// Get a WIT client to make REST calls to Azure DevOps Services
return WorkItemTrackingRestClient.getClient().getQuery(projectId, "Shared Queries/Feedback")
.then(function (query) {
// Process query data (implemented in Step 4)
return WidgetHelpers.WidgetStatusHelper.Success();
}, function (error) {
return WidgetHelpers.WidgetStatusHelper.Failure(error.message);
});
}
return {
load: function (widgetSettings) {
// Set your title
var $title = $('h2.title');
$title.text('Hello World');
return getQueryInfo(widgetSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
Détails de l’implémentation clé
| Composant | Objectif |
|---|---|
WorkItemTrackingRestClient.getClient() |
Obtient une instance du client REST Work Item Tracking |
getQuery() |
Récupère les informations de requête encapsulées dans une promesse |
WidgetStatusHelper.Failure() |
Fournit une gestion cohérente des erreurs pour les défaillances de widgets |
projectId |
Contexte de projet actuel requis pour les appels d’API |
Conseil
Chemins de requête personnalisés : si vous n’avez pas de requête « Commentaires » dans « Requêtes partagées », remplacez "Shared Queries/Feedback" par le chemin d’accès à une requête qui existe dans votre projet.
Étape 4 : Afficher les données de réponse de l’API
Affichez les informations de requête dans votre widget en traitant la réponse de l’API REST.
Ajouter un rendu des données de requête
Remplacez le // Process query data commentaire par cette implémentation :
// Create a list with query details
var $list = $('<ul>');
$list.append($('<li>').text("Query ID: " + query.id));
$list.append($('<li>').text("Query Name: " + query.name));
$list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName : "<unknown>")));
// Append the list to the query-info-container
var $container = $('#query-info-container');
$container.empty();
$container.append($list);
La getQuery() méthode retourne un Contracts.QueryHierarchyItem objet avec des propriétés pour les métadonnées de requête. Cet exemple montre comment afficher trois éléments clés d’informations sous le texte « Hello World ».
Exemple de travail complet
Votre fichier final hello-world2.html doit ressembler à ceci :
<!DOCTYPE html>
<html>
<head>
<script src="sdk/scripts/VSS.SDK.min.js"></script>
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers", "AzureDevOps/WorkItemTracking/RestClient"],
function (WidgetHelpers, WorkItemTrackingRestClient) {
WidgetHelpers.IncludeWidgetStyles();
VSS.register("HelloWorldWidget2", function () {
var projectId = VSS.getWebContext().project.id;
var getQueryInfo = function (widgetSettings) {
// Get a WIT client to make REST calls to Azure DevOps Services
return WorkItemTrackingRestClient.getClient().getQuery(projectId, "Shared Queries/Feedback")
.then(function (query) {
// Create a list with query details
var $list = $('<ul>');
$list.append($('<li>').text("Query ID: " + query.id));
$list.append($('<li>').text("Query Name: " + query.name));
$list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName : "<unknown>")));
// Append the list to the query-info-container
var $container = $('#query-info-container');
$container.empty();
$container.append($list);
// Use the widget helper and return success as Widget Status
return WidgetHelpers.WidgetStatusHelper.Success();
}, function (error) {
// Use the widget helper and return failure as Widget Status
return WidgetHelpers.WidgetStatusHelper.Failure(error.message);
});
}
return {
load: function (widgetSettings) {
// Set your title
var $title = $('h2.title');
$title.text('Hello World');
return getQueryInfo(widgetSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
</head>
<body>
<div class="widget">
<h2 class="title"></h2>
<div id="query-info-container"></div>
</div>
</body>
</html>
Étape 5 : Mettre à jour le manifeste d’extension
Pour le rendre disponible dans le catalogue de widgets, ajoutez votre nouveau widget au manifeste d’extension.
Ajouter la deuxième contribution du widget
Mettez à jour vss-extension.json pour inclure votre widget COMPATIBLE AVEC l’API REST. Ajoutez cette contribution au contributions tableau :
{
"contributions": [
// ...existing HelloWorldWidget contribution...,
{
"id": "HelloWorldWidget2",
"type": "ms.vss-dashboards-web.widget",
"targets": [
"ms.vss-dashboards-web.widget-catalog"
],
"properties": {
"name": "Hello World Widget 2 (with API)",
"description": "My second widget",
"previewImageUrl": "img/preview2.png",
"uri": "hello-world2.html",
"supportedSizes": [
{
"rowSpan": 1,
"columnSpan": 2
}
],
"supportedScopes": ["project_team"]
}
}
],
"files": [
{
"path": "hello-world.html",
"addressable": true
},
{
"path": "hello-world2.html",
"addressable": true
},
{
"path": "sdk/scripts",
"addressable": true
},
{
"path": "img",
"addressable": true
}
],
"scopes": [
"vso.work"
]
}
Conseil
Image d’aperçu : créez une preview2.png image (330 x 160 pixels) et placez-la dans le img dossier pour afficher aux utilisateurs l’apparence de votre widget dans le catalogue.
Étape 6 : Empaqueter, publier et partager
Créez un package, publiez et partagez votre extension. Si vous avez déjà publié l’extension, vous pouvez repackager et la mettre à jour directement sur la Place de marché.
Étape 7 : Tester votre widget d’API REST
Pour afficher l’intégration de l’API REST en action, ajoutez le nouveau widget à votre tableau de bord :
- Accédez à votre projet Azure DevOps :
https://dev.azure.com/{Your_Organization}/{Your_Project}. - Veuillez sélectionner Aperçu des >tableaux de bord.
- Sélectionnez Ajouter un widget.
- Recherchez « Hello World Widget 2 (avec API) », puis sélectionnez Ajouter.
Votre widget amélioré affiche à la fois le texte « Hello World » et les informations de requête dynamiques de votre projet Azure DevOps.
Étapes suivantes : passez à la partie 3 pour ajouter des options de configuration qui permettent aux utilisateurs de personnaliser la requête à afficher.
Partie 3 : Configurer Hello World
Enrichissez la partie 2 en ajoutant des options de configuration utilisateur à votre widget. Au lieu de coder en dur le chemin de requête, créez une interface de configuration qui permet aux utilisateurs de sélectionner la requête à afficher, avec la fonctionnalité d’aperçu en direct.
Cette partie montre comment créer des widgets configurables que les utilisateurs peuvent personnaliser en fonction de leurs besoins spécifiques tout en fournissant des commentaires en temps réel pendant la configuration.
Étape 1 : Créer des fichiers de configuration
Les configurations de widget partagent de nombreuses similitudes avec les widgets eux-mêmes : elles utilisent les mêmes sdk, la même structure HTML et les modèles JavaScript, mais servent des objectifs différents dans l’infrastructure d’extension.
Configurer la structure du projet
Pour prendre en charge la configuration du widget, créez deux nouveaux fichiers :
- Copiez
hello-world2.htmlet renommez-le enhello-world3.html, votre widget configurable. - Créez un fichier appelé
configuration.html, qui gère l’interface de configuration.
Votre structure de projet inclut désormais les éléments suivants :
|--- README.md
|--- sdk/
|--- node_modules
|--- scripts/
|--- VSS.SDK.min.js
|--- img/
|--- logo.png
|--- scripts/
|--- configuration.html // New: Configuration interface
|--- hello-world.html // Part 1: Basic widget
|--- hello-world2.html // Part 2: REST API widget
|--- hello-world3.html // Part 3: Configurable widget (new)
|--- vss-extension.json // Extension manifest
Créer l’interface de configuration
Ajoutez cette structure HTML à configuration.html, ce qui crée un sélecteur de liste déroulante pour choisir des requêtes :
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="sdk/scripts/VSS.SDK.min.js"></script>
</head>
<body>
<div class="container">
<fieldset>
<label class="label">Query: </label>
<select id="query-path-dropdown" style="margin-top:10px">
<option value="" selected disabled hidden>Please select a query</option>
<option value="Shared Queries/Feedback">Shared Queries/Feedback</option>
<option value="Shared Queries/My Bugs">Shared Queries/My Bugs</option>
<option value="Shared Queries/My Tasks">Shared Queries/My Tasks</option>
</select>
</fieldset>
</div>
</body>
</html>
Étape 2 : Implémenter la configuration JavaScript
Configuration JavaScript suit le même modèle d’initialisation que les widgets, mais implémente le IWidgetConfiguration contrat au lieu du contrat de base IWidget .
Ajouter une logique de configuration
Insérez ce script dans la <head> section suivante :configuration.html
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers"], function (WidgetHelpers) {
VSS.register("HelloWorldWidget.Configuration", function () {
var $queryDropdown = $("#query-path-dropdown");
return {
load: function (widgetSettings, widgetConfigurationContext) {
var settings = JSON.parse(widgetSettings.customSettings.data);
if (settings && settings.queryPath) {
$queryDropdown.val(settings.queryPath);
}
return WidgetHelpers.WidgetStatusHelper.Success();
},
onSave: function() {
var customSettings = {
data: JSON.stringify({
queryPath: $queryDropdown.val()
})
};
return WidgetHelpers.WidgetConfigurationSave.Valid(customSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
Détails du contrat de configuration
Le IWidgetConfiguration contrat nécessite ces fonctions clés :
| Fonction | Objectif | Moment de l’appel |
|---|---|---|
load() |
Initialiser l’interface utilisateur de configuration avec les paramètres existants | Lorsque la boîte de dialogue de configuration s’ouvre |
onSave() |
Sérialiser les paramètres d’entrée utilisateur et de validation | Lorsque l’utilisateur sélectionne Enregistrer |
Conseil
Sérialisation des données : cet exemple utilise JSON pour sérialiser les paramètres. Le widget accède à ces paramètres via widgetSettings.customSettings.data et doit les désérialiser en conséquence.
Étape 3 : Activer la fonctionnalité d’aperçu en direct
La préversion dynamique permet aux utilisateurs d’afficher les modifications du widget immédiatement quand ils modifient les paramètres de configuration, en fournissant des commentaires instantanés avant l’enregistrement.
Implémenter des notifications de modification
Pour activer la préversion en direct, ajoutez ce gestionnaire d’événements dans la load fonction :
$queryDropdown.on("change", function () {
var customSettings = {
data: JSON.stringify({
queryPath: $queryDropdown.val()
})
};
var eventName = WidgetHelpers.WidgetEvent.ConfigurationChange;
var eventArgs = WidgetHelpers.WidgetEvent.Args(customSettings);
widgetConfigurationContext.notify(eventName, eventArgs);
});
Terminer le fichier de configuration
Votre résultat final configuration.html doit ressembler à ceci :
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="sdk/scripts/VSS.SDK.min.js"></script>
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers"], function (WidgetHelpers) {
VSS.register("HelloWorldWidget.Configuration", function () {
var $queryDropdown = $("#query-path-dropdown");
return {
load: function (widgetSettings, widgetConfigurationContext) {
var settings = JSON.parse(widgetSettings.customSettings.data);
if (settings && settings.queryPath) {
$queryDropdown.val(settings.queryPath);
}
$queryDropdown.on("change", function () {
var customSettings = {data: JSON.stringify({queryPath: $queryDropdown.val()})};
var eventName = WidgetHelpers.WidgetEvent.ConfigurationChange;
var eventArgs = WidgetHelpers.WidgetEvent.Args(customSettings);
widgetConfigurationContext.notify(eventName, eventArgs);
});
return WidgetHelpers.WidgetStatusHelper.Success();
},
onSave: function() {
var customSettings = {data: JSON.stringify({queryPath: $queryDropdown.val()})};
return WidgetHelpers.WidgetConfigurationSave.Valid(customSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
</head>
<body>
<div class="container">
<fieldset>
<label class="label">Query: </label>
<select id="query-path-dropdown" style="margin-top:10px">
<option value="" selected disabled hidden>Please select a query</option>
<option value="Shared Queries/Feedback">Shared Queries/Feedback</option>
<option value="Shared Queries/My Bugs">Shared Queries/My Bugs</option>
<option value="Shared Queries/My Tasks">Shared Queries/My Tasks</option>
</select>
</fieldset>
</div>
</body>
</html>
Importante
Activer le bouton Enregistrer : l’infrastructure nécessite au moins une notification de modification de configuration pour activer le bouton Enregistrer . Le gestionnaire d’événements de modification garantit que cette action se produit lorsque les utilisateurs sélectionnent une option.
Étape 4 : Rendre le widget configurable
Transformez votre widget de la partie 2 pour utiliser des données de configuration au lieu de valeurs codées en dur. Cette étape nécessite l’implémentation du IConfigurableWidget contrat.
Mettre à jour l’inscription des widgets
Dans hello-world3.html, apportez ces modifications :
-
Mettre à jour l’ID du widget : passer de
HelloWorldWidget2àHelloWorldWidget3. -
Ajouter une fonction de rechargement : implémentez le
IConfigurableWidgetcontrat.
return {
load: function (widgetSettings) {
// Set your title
var $title = $('h2.title');
$title.text('Hello World');
return getQueryInfo(widgetSettings);
},
reload: function (widgetSettings) {
return getQueryInfo(widgetSettings);
}
}
Gérer les données de configuration
Mettez à jour la getQueryInfo fonction pour utiliser les paramètres de configuration au lieu des chemins de requête codés en dur :
var settings = JSON.parse(widgetSettings.customSettings.data);
if (!settings || !settings.queryPath) {
var $container = $('#query-info-container');
$container.empty();
$container.text("Please configure a query path to display data.");
return WidgetHelpers.WidgetStatusHelper.Success();
}
Différences de cycle de vie des widgets
| Fonction | Objectif | Instructions d’utilisation |
|---|---|---|
load() |
Rendu initial du widget et configuration ponctuelle | Opérations lourdes, initialisation des ressources |
reload() |
Mettre à jour le widget avec une nouvelle configuration | Mises à jour légères, actualisation des données |
Conseil
Optimisation des performances : utilisez load() pour les opérations coûteuses qui n’ont besoin d’être exécutées qu’une seule fois et reload() pour les mises à jour rapides lorsque la configuration change.
(Facultatif) Ajouter un lightbox pour obtenir des informations détaillées
Les widgets de tableau de bord ont un espace limité, ce qui rend difficile l’affichage d’informations complètes. Un lightbox fournit une solution élégante en affichant des données détaillées dans une superposition modale sans naviguer loin du tableau de bord.
Pourquoi utiliser un lightbox dans des widgets ?
| Avantage | Descriptif |
|---|---|
| Optimisation de l’espace | Conserver le widget compact tout en offrant des vues détaillées |
| Expérience utilisateur | Gérer le contexte du tableau de bord tout en affichant plus d’informations |
| Affichage progressif | Afficher les données récapitulatives dans le widget, détails à la demande |
| Conception réactive | Adapter à différentes tailles d’écran et configurations de widget |
Implémenter des éléments cliquables
Mettez à jour votre rendu des données de requête pour inclure des éléments cliquables qui déclenchent la boîte de réception :
// Create a list with clickable query details
var $list = $('<ul class="query-summary">');
$list.append($('<li>').text("Query ID: " + query.id));
$list.append($('<li>').text("Query Name: " + query.name));
$list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName : "<unknown>"));
// Add a clickable element to open detailed view
var $detailsLink = $('<button class="details-link">View Details</button>');
$detailsLink.on('click', function() {
showQueryDetails(query);
});
// Append to the container
var $container = $('#query-info-container');
$container.empty();
$container.append($list);
$container.append($detailsLink);
Créer la fonctionnalité lightbox
Ajoutez cette implémentation lightbox à votre widget JavaScript :
function showQueryDetails(query) {
// Create lightbox overlay
var $overlay = $('<div class="lightbox-overlay">');
var $lightbox = $('<div class="lightbox-content">');
// Add close button
var $closeBtn = $('<button class="lightbox-close">×</button>');
$closeBtn.on('click', function() {
$overlay.remove();
});
// Create detailed content
var $content = $('<div class="query-details">');
$content.append($('<h3>').text(query.name || 'Query Details'));
$content.append($('<p>').html('<strong>ID:</strong> ' + query.id));
$content.append($('<p>').html('<strong>Path:</strong> ' + query.path));
$content.append($('<p>').html('<strong>Created:</strong> ' + (query.createdDate ? new Date(query.createdDate).toLocaleDateString() : 'Unknown')));
$content.append($('<p>').html('<strong>Modified:</strong> ' + (query.lastModifiedDate ? new Date(query.lastModifiedDate).toLocaleDateString() : 'Unknown')));
$content.append($('<p>').html('<strong>Created By:</strong> ' + (query.createdBy ? query.createdBy.displayName : 'Unknown')));
$content.append($('<p>').html('<strong>Modified By:</strong> ' + (query.lastModifiedBy ? query.lastModifiedBy.displayName : 'Unknown')));
if (query.queryType) {
$content.append($('<p>').html('<strong>Type:</strong> ' + query.queryType));
}
// Assemble lightbox
$lightbox.append($closeBtn);
$lightbox.append($content);
$overlay.append($lightbox);
// Add to document and show
$('body').append($overlay);
// Close on overlay click
$overlay.on('click', function(e) {
if (e.target === $overlay[0]) {
$overlay.remove();
}
});
// Close on Escape key
$(document).on('keydown.lightbox', function(e) {
if (e.keyCode === 27) { // Escape key
$overlay.remove();
$(document).off('keydown.lightbox');
}
});
}
Ajoutez un style de type « lightbox »
Incluez des styles CSS pour la zone de lumière dans la section HTML <head> de votre widget :
<style>
.query-summary {
list-style: none;
padding: 0;
margin: 10px 0;
}
.query-summary li {
padding: 2px 0;
font-size: 12px;
}
.details-link {
background: #0078d4;
color: white;
border: none;
padding: 4px 8px;
font-size: 11px;
cursor: pointer;
border-radius: 2px;
margin-top: 8px;
}
.details-link:hover {
background: #106ebe;
}
.lightbox-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
}
.lightbox-content {
background: white;
border-radius: 4px;
padding: 20px;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
position: relative;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.lightbox-close {
position: absolute;
top: 10px;
right: 15px;
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #666;
line-height: 1;
}
.lightbox-close:hover {
color: #000;
}
.query-details h3 {
margin-top: 0;
color: #323130;
}
.query-details p {
margin: 8px 0;
font-size: 14px;
line-height: 1.4;
}
</style>
Implémentation améliorée du widget
Votre widget amélioré complet avec la fonctionnalité lightbox :
<!DOCTYPE html>
<html>
<head>
<script src="sdk/scripts/VSS.SDK.min.js"></script>
<style>
/* Lightbox styles from above */
.query-summary {
list-style: none;
padding: 0;
margin: 10px 0;
}
.query-summary li {
padding: 2px 0;
font-size: 12px;
}
.details-link {
background: #0078d4;
color: white;
border: none;
padding: 4px 8px;
font-size: 11px;
cursor: pointer;
border-radius: 2px;
margin-top: 8px;
}
.details-link:hover {
background: #106ebe;
}
.lightbox-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
}
.lightbox-content {
background: white;
border-radius: 4px;
padding: 20px;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
position: relative;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.lightbox-close {
position: absolute;
top: 10px;
right: 15px;
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #666;
line-height: 1;
}
.lightbox-close:hover {
color: #000;
}
.query-details h3 {
margin-top: 0;
color: #323130;
}
.query-details p {
margin: 8px 0;
font-size: 14px;
line-height: 1.4;
}
</style>
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers", "AzureDevOps/WorkItemTracking/RestClient"],
function (WidgetHelpers, WorkItemTrackingRestClient) {
WidgetHelpers.IncludeWidgetStyles();
function showQueryDetails(query) {
// Lightbox implementation from above
}
VSS.register("HelloWorldWidget2", function () {
var projectId = VSS.getWebContext().project.id;
var getQueryInfo = function (widgetSettings) {
return WorkItemTrackingRestClient.getClient().getQuery(projectId, "Shared Queries/Feedback")
.then(function (query) {
// Enhanced display with lightbox trigger
var $list = $('<ul class="query-summary">');
$list.append($('<li>').text("Query ID: " + query.id));
$list.append($('<li>').text("Query Name: " + query.name));
$list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName : "<unknown>")));
var $detailsLink = $('<button class="details-link">View Details</button>');
$detailsLink.on('click', function() {
showQueryDetails(query);
});
var $container = $('#query-info-container');
$container.empty();
$container.append($list);
$container.append($detailsLink);
return WidgetHelpers.WidgetStatusHelper.Success();
}, function (error) {
return WidgetHelpers.WidgetStatusHelper.Failure(error.message);
});
}
return {
load: function (widgetSettings) {
// Set your title
var $title = $('h2.title');
$title.text('Hello World');
return getQueryInfo(widgetSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
</head>
<body>
<div class="widget">
<h2 class="title"></h2>
<div id="query-info-container"></div>
</div>
</body>
</html>
Considérations relatives à l’accessibilité : assurez-vous que votre lightbox est accessible au clavier et inclut des étiquettes appropriées pour les lecteurs d’écran. Testez avec les fonctionnalités d’accessibilité intégrées d’Azure DevOps.
Importante
Performances : les « lightboxes » doivent se charger rapidement. Envisagez le chargement différé des données détaillées uniquement à l’ouverture de la « lightbox », au lieu de tout charger dès le départ.
Étape 5 : Configurer le manifeste d’extension
Inscrivez le widget configurable et son interface de configuration dans votre manifeste d’extension.
Ajoutez des contributions de widget et de configuration
Mise à jour vss-extension.json pour inclure deux nouvelles contributions :
{
"contributions": [
{
"id": "HelloWorldWidget3",
"type": "ms.vss-dashboards-web.widget",
"targets": [
"ms.vss-dashboards-web.widget-catalog",
"fabrikam.azuredevops-extensions-myExtensions.HelloWorldWidget.Configuration"
],
"properties": {
"name": "Hello World Widget 3 (with config)",
"description": "My third widget",
"previewImageUrl": "img/preview3.png",
"uri": "hello-world3.html",
"supportedSizes": [
{
"rowSpan": 1,
"columnSpan": 2
},
{
"rowSpan": 2,
"columnSpan": 2
}
],
"supportedScopes": ["project_team"]
}
},
{
"id": "HelloWorldWidget.Configuration",
"type": "ms.vss-dashboards-web.widget-configuration",
"targets": [ "ms.vss-dashboards-web.widget-configuration" ],
"properties": {
"name": "HelloWorldWidget Configuration",
"description": "Configures HelloWorldWidget",
"uri": "configuration.html"
}
}
],
"files": [
{
"path": "hello-world.html", "addressable": true
},
{
"path": "hello-world2.html", "addressable": true
},
{
"path": "hello-world3.html", "addressable": true
},
{
"path": "configuration.html", "addressable": true
},
{
"path": "sdk/scripts", "addressable": true
},
{
"path": "img", "addressable": true
}
]
}
Exigences de contribution à la configuration
| Propriété | Objectif | Valeur requise |
|---|---|---|
type |
Identifie la contribution en tant que configuration de widget | ms.vss-dashboards-web.widget-configuration |
targets |
Où la configuration s’affiche | ms.vss-dashboards-web.widget-configuration |
uri |
Chemin d’accès au fichier HTML de configuration | Chemin de votre fichier de configuration |
Modèle de ciblage de widget
Pour les widgets configurables, le targets tableau doit inclure une référence à la configuration :
<publisher>.<extension-id>.<configuration-id>
Avertissement
Visibilité du bouton de configuration : si le widget ne cible pas correctement sa contribution de configuration, le bouton Configurer n’apparaît pas. Vérifiez que les noms de l’éditeur et de l’extension correspondent exactement à votre manifeste.
Étape 6 : Empaqueter, publier et partager
Déployez votre extension améliorée avec des fonctionnalités de configuration.
S’il s’agit de votre première publication, suivez l’étape 6 : Empaqueter, publier et partager. Pour les extensions existantes, repackagez et mettez à jour directement dans la Place de marché.
Étape 7 : Tester le widget configurable
Découvrez le flux de travail de configuration complet en ajoutant et en configurant votre widget.
Ajoutez le widget à votre tableau de bord.
- Accédez à
https://dev.azure.com/{Your_Organization}/{Your_Project}. - Rendez-vous dans Vue d’ensemble>Tableaux de bord.
- Sélectionnez Ajouter un widget.
- Recherchez « Hello World Widget 3 (avec configuration) », puis sélectionnez Ajouter.
Une invite de configuration s’affiche, car le widget nécessite une configuration :
Configurer le widget
Configuration d’accès via l’une ou l’autre méthode :
- Menu widget : pointez sur le widget, sélectionnez les points de suspension (⋯), puis configurez
- Mode d’édition du tableau de bord : sélectionnez Modifier dans le tableau de bord, puis le bouton configurer sur le widget
Le panneau de configuration s’ouvre avec un aperçu en direct au centre. Sélectionnez une requête dans la liste déroulante pour afficher les mises à jour immédiates, puis sélectionnez Enregistrer pour appliquer vos modifications.
Étape 8 : Ajouter des options de configuration avancées
Étendez votre widget avec des fonctionnalités de configuration intégrées telles que des noms et des tailles personnalisés.
Activer la configuration du nom et de la taille
Azure DevOps fournit deux fonctionnalités configurables prêtes à l’emploi :
| Caractéristique | Propriété du manifeste | Objectif |
|---|---|---|
| Noms personnalisés | isNameConfigurable: true |
Les utilisateurs peuvent remplacer le nom du widget par défaut |
| Tailles multiples | Plusieurs entrées supportedSizes |
Les utilisateurs peuvent redimensionner des widgets |
Exemple enrichi de manifeste
{
"contributions": [
{
"id": "HelloWorldWidget3",
"type": "ms.vss-dashboards-web.widget",
"targets": [
"ms.vss-dashboards-web.widget-catalog",
"fabrikam.azuredevops-extensions-myExtensions.HelloWorldWidget.Configuration"
],
"properties": {
"name": "Hello World Widget 3 (with config)",
"description": "My third widget",
"previewImageUrl": "img/preview3.png",
"uri": "hello-world3.html",
"isNameConfigurable": true,
"supportedSizes": [
{
"rowSpan": 1,
"columnSpan": 2
},
{
"rowSpan": 2,
"columnSpan": 2
}
],
"supportedScopes": ["project_team"]
}
}
]
}
Afficher les noms configurés
Pour afficher les noms de widgets personnalisés, mettez à jour votre widget pour utiliser widgetSettings.name:
return {
load: function (widgetSettings) {
// Display configured name instead of hard-coded text
var $title = $('h2.title');
$title.text(widgetSettings.name);
return getQueryInfo(widgetSettings);
},
reload: function (widgetSettings) {
// Update name during configuration changes
var $title = $('h2.title');
$title.text(widgetSettings.name);
return getQueryInfo(widgetSettings);
}
}
Après avoir mis à jour votre extension, vous pouvez configurer le nom et la taille du widget :
Repackagez et mettez à jour votre extension pour activer ces options de configuration avancées.
Félicitations! Vous avez créé un widget de tableau de bord Azure DevOps complet et configurable avec des fonctionnalités d’aperçu en direct et des options de personnalisation utilisateur.