Compartir a través de


Agregar un widget de panel

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

Los widgets se implementan como contribuciones en el marco de extensión. Una sola extensión puede incluir varias contribuciones de widget. En este artículo se muestra cómo crear una extensión que proporcione uno o varios widgets.

Sugerencia

Consulte nuestra documentación más reciente sobre el desarrollo de extensiones mediante el SDK de extensión de Azure DevOps.

Sugerencia

Si va a iniciar una nueva extensión de Azure DevOps, pruebe primero estas colecciones de ejemplo mantenidas: funcionan con compilaciones de productos actuales y cubren escenarios modernos (por ejemplo, agregar pestañas en páginas de solicitud de incorporación de cambios).

Si un ejemplo no funciona en su organización, instálelo en una organización personal o de prueba y compare los identificadores de destino y las versiones de API del manifiesto de extensión con los documentos actuales. Para obtener referencia y API, consulte:

Requisitos previos

Requisito Descripción
Conocimientos de programación Conocimientos de JavaScript, HTML y CSS para el desarrollo de widgets
Organización de Azure DevOps Cree una organización
Editor de texto Usamos Visual Studio Code para tutoriales
Node.js Última versión de Node.js
CLI multiplataforma tfx-cli para empaquetar extensiones
Instalación mediante: npm i -g tfx-cli
Directorio del proyecto Directorio principal con esta estructura después de completar el tutorial:

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

Información general del tutorial

En este tutorial se enseña el desarrollo de widgets a través de tres ejemplos progresivos:

Parte Enfoque Conocimientos que adquirirá
Parte 1: Hola mundo Creación básica de widgets Crear un widget que muestre texto
Parte 2: Integración de la API REST Llamadas API de Azure DevOps Adición de la funcionalidad de la API REST para capturar y mostrar datos
Parte 3: Configuración del widget Personalización del usuario Implementación de opciones de configuración para el widget

Sugerencia

Si prefiere saltar directamente a los ejemplos de trabajo, los ejemplos incluidos (vea la nota anterior) muestran un conjunto de widgets que puede empaquetar y publicar.

Antes de comenzar, revise los estilos básicos de widget y las instrucciones estructurales que proporcionamos.

Parte 1: Hola mundo

Cree un widget básico que muestre "Hola mundo" mediante JavaScript. Esta base muestra los conceptos básicos de desarrollo de widgets.

Captura de pantalla del panel Información general con un widget de ejemplo.

Paso 1: Instalación del SDK de cliente

El SDK de VSS permite que el widget se comunique con Azure DevOps. Instálelo mediante npm:

npm install vss-web-extension-sdk

Copie el VSS.SDK.min.js archivo de vss-web-extension-sdk/lib a la home/sdk/scripts carpeta.

Para más documentación del SDK, consulte la página gitHub del SDK de cliente.

Paso 2: Crear la estructura HTML

Cree hello-world.html en el directorio del proyecto. Este archivo proporciona el diseño del widget y las referencias a los scripts necesarios.

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

Los widgets se ejecutan en iframes, por lo que la mayoría de los elementos principales HTML excepto <script> y <link> se omiten en el marco de trabajo.

Paso 3: Adición de JavaScript widget

Para implementar la funcionalidad del widget, agregue este script a la <head> sección del archivo 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>

Componentes clave de JavaScript

Función Propósito
VSS.init() Inicializa la comunicación entre el widget y Azure DevOps.
VSS.require() Carga las bibliotecas de SDK necesarias y los asistentes de widgets
VSS.register() Registra el widget con un identificador único
WidgetHelpers.IncludeWidgetStyles() Aplica el estilo predeterminado de Azure DevOps.
VSS.notifyLoadSucceeded() Notifica al framework que la carga se completó correctamente.

Importante

El nombre del widget en VSS.register() debe coincidir con el id en el manifiesto de tu extensión (paso 5).

Paso 4: Agregar imágenes de extensión

Cree las imágenes necesarias para la extensión:

  • Logotipo de extensión: imagen de píxeles de 98 x 98 píxeles denominada logo.png en la img carpeta
  • Icono del catálogo de widgets: imagen de píxeles de 98x98 denominada CatalogIcon.png en la img carpeta
  • Vista previa del widget: imagen de 330 x 160 píxeles denominada preview.png en la img carpeta

Estas imágenes se muestran en Marketplace y el catálogo de widgets cuando los usuarios examinan las extensiones disponibles.

Paso 5: Crear el manifiesto de extensión

Cree vss-extension.json en el directorio raíz del proyecto. Este archivo define los metadatos y las contribuciones de la extensión:

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

Reemplace por "publisher": "fabrikam" el nombre real del publicador. Aprenda a crear un publicador.

Propiedades esenciales del manifiesto

Sección Propósito
Información básica Nombre de extensión, versión, descripción y publicador
Iconos Rutas de acceso a los recursos visuales de la extensión
Contribuciones Definiciones de widgets, como el identificador, el tipo y las propiedades
Archivos Todos los archivos que se van a incluir en el paquete de extensión

Para obtener documentación completa del manifiesto, consulte Referencia del manifiesto de extensión.

Paso 6: Empaquetar y publicar la extensión

Empaquete la extensión y publíquela en Visual Studio Marketplace.

Instalación de la herramienta de empaquetado

npm i -g tfx-cli

Creación del paquete de extensión

En el directorio del proyecto, ejecute:

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

Esta acción crea un .vsix archivo que contiene la extensión empaquetada.

Configuración de un publicador

  1. Vaya al Portal de publicación de Visual Studio Marketplace.
  2. Inicie sesión y cree un publicador si no tiene uno.
  3. Elija un identificador de publicador único (usado en el archivo de manifiesto).
  4. Actualice su vss-extension.json para utilizar su nombre de editor en lugar de "fabrikam".

Cargue su extensión

  1. En el Portal de publicación, seleccione Cargar nueva extensión.
  2. Elija el .vsix archivo y cárguelo.
  3. Comparta la extensión con su organización de Azure DevOps.

Como alternativa, use la línea de comandos:

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

Sugerencia

Use --rev-version para incrementar automáticamente el número de versión al actualizar una extensión existente.

Paso 7: Instalar y probar el widget

Para probarlo, agregue el widget a un panel:

  1. Vaya al proyecto de Azure DevOps: https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Vaya a Información general>Paneles.
  3. Selecciona Agregar un widget.
  4. Busque el widget en el catálogo y seleccione Agregar.

El widget "Hola mundo" aparece en el panel, mostrando el texto que configuró.

Paso siguiente: continúe con la parte 2 para aprender a integrar las API rest de Azure DevOps en el widget.

Parte 2: Hola mundo con la API REST de Azure DevOps

Amplíe el widget para interactuar con los datos de Azure DevOps mediante las API REST. En este ejemplo se muestra cómo capturar información de consulta y mostrarla dinámicamente en el widget.

En esta parte, use la API REST de seguimiento de elementos de trabajo para recuperar información sobre una consulta existente y mostrar los detalles de la consulta debajo del texto "Hola mundo".

Captura de pantalla del panel Información general con un widget de ejemplo mediante la API REST para WorkItemTracking.

Paso 1: Crear el archivo HTML mejorado

Cree un nuevo archivo de widget que se basa en el ejemplo anterior. Copie hello-world.html y cámbiele el nombre a hello-world2.html. La estructura del proyecto ahora incluye:

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

Actualización de la estructura HTML del widget

Realice estos cambios en hello-world2.html:

  1. Agregar un contenedor para los datos de consulta: incluya un nuevo <div> elemento para mostrar información de consulta.
  2. Actualice el identificador del widget: cambie el nombre del widget de HelloWorldWidget a HelloWorldWidget2 para la identificación única.
<!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>

Paso 2: Configuración de permisos de acceso de API

Antes de realizar llamadas a la API REST, configure los permisos necesarios en el manifiesto de extensión.

Adición del ámbito de trabajo

El ámbito vso.work concede acceso de solo lectura a los elementos de trabajo y las consultas. Agregue este ámbito a vss-extension.json:

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

Ejemplo de manifiesto completo

Para obtener un manifiesto completo con otras propiedades, estructurelo de la siguiente manera:

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

Importante

Limitaciones de ámbito: no se admite agregar o cambiar ámbitos después de publicar. Si ya ha publicado la extensión, primero debe quitarla de Marketplace. Vaya al Portal de publicación de Visual Studio Marketplace, busque la extensión y seleccione Quitar.

Paso 3: Implementación de la integración de la API REST

Azure DevOps proporciona bibliotecas cliente REST de JavaScript a través del SDK. Estas bibliotecas encapsulan las llamadas AJAX y asignan respuestas de API a objetos utilizables.

Actualización del widget de JavaScript

Sustituya la llamada VSS.require en su hello-world2.html para incluir el cliente REST de seguimiento de elementos de trabajo:

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

Detalles clave de implementación

Componente Propósito
WorkItemTrackingRestClient.getClient() Obtenga una instancia del cliente REST de seguimiento de elementos de trabajo
getQuery() Recupera la información de consulta encapsulada en una promesa.
WidgetStatusHelper.Failure() Proporciona una gestión de errores coherente para fallos de widget
projectId Contexto actual del proyecto necesario para las llamadas API

Sugerencia

Rutas de acceso de consulta personalizadas: si no tiene una consulta de "Comentarios" en "Consultas compartidas", reemplace por "Shared Queries/Feedback" la ruta de acceso a cualquier consulta que exista en el proyecto.

Paso 4: Mostrar datos de respuesta de API

Represente la información de consulta en el widget mediante el procesamiento de la respuesta de la API REST.

Agregar representación de datos de consulta

Reemplace el // Process query data comentario por esta implementación:

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

El getQuery() método devuelve un Contracts.QueryHierarchyItem objeto con propiedades para los metadatos de consulta. En este ejemplo se muestran tres fragmentos clave de información debajo del texto "Hola mundo".

Ejemplo de trabajo completo

El archivo final hello-world2.html debe tener este aspecto:

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

Paso 5: Actualizar el manifiesto de extensión

Para que esté disponible en el catálogo de widgets, agregue el nuevo widget al manifiesto de extensión.

Adición de la segunda contribución del widget

Actualice vss-extension.json para incluir el widget habilitado para la API REST. Agregue esta contribución a la contributions matriz:

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

Sugerencia

Imagen de vista previa: cree una preview2.png imagen (330 x 160 píxeles) y colóquela en la img carpeta para mostrar a los usuarios el aspecto del widget en el catálogo.

Paso 6: Empaquetar, publicar y compartir

Empaquete, publique y comparta su extensión. Si ya ha publicado la extensión, puede volver a empaquetar y actualizarla directamente en Marketplace.

Paso 7: Probar el widget de la API REST

Para ver la integración de la API REST en acción, agregue el nuevo widget al panel:

  1. Vaya al proyecto de Azure DevOps: https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Seleccione Overview>Dashboards.
  3. Selecciona Agregar un widget.
  4. Busque "Hello World Widget 2 (con API)" y seleccione Agregar.

El widget mejorado muestra tanto el texto "Hola mundo" como la información de consulta dinámica del proyecto de Azure DevOps.

Pasos siguientes: continúe con la parte 3 para agregar opciones de configuración que permiten a los usuarios personalizar qué consulta mostrar.

Parte 3: Configurar Hola mundo

Amplíe Parte 2 añadiendo capacidades de configuración de usuario a su widget. En lugar de codificar de forma rígida la ruta de acceso de consulta, cree una interfaz de configuración que permita a los usuarios seleccionar qué consulta mostrar, con la funcionalidad de vista previa activa.

En esta parte se muestra cómo crear widgets configurables que los usuarios pueden personalizar para sus necesidades específicas al proporcionar comentarios en tiempo real durante la configuración.

Captura de pantalla de la vista previa en directo del panel de información general del widget en función de los cambios.

Paso 1: Crear archivos de configuración

Las configuraciones de widget comparten muchas similitudes con los propios widgets, tanto usan el mismo SDK, estructura HTML y patrones de JavaScript, pero sirven para propósitos diferentes dentro del marco de extensión.

Configuración de la estructura del proyecto

Para admitir la configuración del widget, cree dos nuevos archivos:

  1. Copie hello-world2.html y cámbiele el nombre a hello-world3.html, el widget configurable.
  2. Cree un archivo denominado configuration.html, que controla la interfaz de configuración.

La estructura del proyecto ahora incluye:

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

Creación de la interfaz de configuración

Agregue esta estructura HTML a configuration.html, que crea un selector desplegable para elegir consultas:

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

Paso 2: Implementación de la configuración de JavaScript

La configuración de JavaScript sigue el mismo patrón de inicialización que los widgets, pero implementa el IWidgetConfiguration contrato en lugar del contrato básico IWidget .

Agrega lógica de configuración

Inserte este script en la <head> sección de 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>

Detalles del contrato de configuración

El IWidgetConfiguration contrato requiere estas funciones clave:

Función Propósito Cuándo se llama
load() Inicialización de la interfaz de usuario de configuración con las opciones existentes Cuando se abre el cuadro de diálogo de configuración
onSave() Serializar la entrada del usuario y validar la configuración Cuando el usuario selecciona Guardar

Sugerencia

Serialización de datos: en este ejemplo se usa JSON para serializar la configuración. El widget accede a esta configuración a través de widgetSettings.customSettings.data y debe deserializarla en consecuencia.

Paso 3: Habilitación de la funcionalidad de vista previa dinámica

La versión preliminar activa permite a los usuarios ver los cambios de widget inmediatamente a medida que modifican las opciones de configuración, proporcionando comentarios instantáneos antes de guardarlos.

Implementación de notificaciones de cambios

Para habilitar la versión preliminar en directo, agregue este controlador de eventos dentro de la load función :

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

Archivo de configuración completo

El resultado final configuration.html debería tener este aspecto:

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

Habilitar el botón Guardar: el marco requiere al menos una notificación de cambio de configuración para habilitar el botón Guardar . El controlador de eventos de cambio garantiza que esta acción se produce cuando los usuarios seleccionan una opción.

Paso 4: Hacer que el widget sea configurable

Transforme el widget de la parte 2 para usar datos de configuración en lugar de valores codificados de forma rígida. Este paso requiere implementar el IConfigurableWidget contrato.

Actualización del registro de widgets

En hello-world3.html, realice estos cambios:

  1. Actualizar el identificador del widget: cambie de HelloWorldWidget2 a HelloWorldWidget3.
  2. Añadir función de recarga: Implementa el contrato IConfigurableWidget.
return {
    load: function (widgetSettings) {
        // Set your title
        var $title = $('h2.title');
        $title.text('Hello World');

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

Control de los datos de configuración

Actualice la getQueryInfo función para usar opciones de configuración en lugar de rutas de consulta codificadas de forma rígida:

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

Diferencias de ciclo de vida del widget

Función Propósito Directrices de uso
load() Representación inicial de widgets y configuración única Operaciones intensivas, inicialización de recursos
reload() Actualizar widget con nueva configuración Actualizaciones ligeras, actualización de datos

Sugerencia

Optimización del rendimiento: use load() para operaciones costosas que solo necesiten ejecutarse una vez y reload() para actualizaciones rápidas cuando cambie la configuración.

(Opcional) Agregar una caja de luz para obtener información detallada

Los widgets de panel tienen un espacio limitado, por lo que resulta difícil mostrar información completa. Una caja de luz proporciona una solución elegante mostrando datos detallados en una superposición modal sin salir del panel.

¿Por qué usar una caja de luz en widgets?

Ventajas Descripción
Ahorro de espacio Mantener el widget compacto al ofrecer vistas detalladas
Experiencia del usuario Mantener el contexto del panel al mostrar más información
Divulgación progresiva Mostrar datos de resumen en widget, detalles a petición
Diseño con capacidad de respuesta Adaptarse a diferentes tamaños de pantalla y configuraciones de widgets

Implementar elementos en los que se pueden hacer clic

Actualice la representación de datos de consulta para incluir elementos en los que se pueden hacer clic que desencadenan la caja de luz:

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

Creación de la funcionalidad lightbox

Agregue esta implementación de lightbox al widget JavaScript:

function showQueryDetails(query) {
    // Create lightbox overlay
    var $overlay = $('<div class="lightbox-overlay">');
    var $lightbox = $('<div class="lightbox-content">');
    
    // Add close button
    var $closeBtn = $('<button class="lightbox-close">&times;</button>');
    $closeBtn.on('click', function() {
        $overlay.remove();
    });
    
    // Create detailed content
    var $content = $('<div class="query-details">');
    $content.append($('<h3>').text(query.name || 'Query Details'));
    $content.append($('<p>').html('<strong>ID:</strong> ' + query.id));
    $content.append($('<p>').html('<strong>Path:</strong> ' + query.path));
    $content.append($('<p>').html('<strong>Created:</strong> ' + (query.createdDate ? new Date(query.createdDate).toLocaleDateString() : 'Unknown')));
    $content.append($('<p>').html('<strong>Modified:</strong> ' + (query.lastModifiedDate ? new Date(query.lastModifiedDate).toLocaleDateString() : 'Unknown')));
    $content.append($('<p>').html('<strong>Created By:</strong> ' + (query.createdBy ? query.createdBy.displayName : 'Unknown')));
    $content.append($('<p>').html('<strong>Modified By:</strong> ' + (query.lastModifiedBy ? query.lastModifiedBy.displayName : 'Unknown')));
    
    if (query.queryType) {
        $content.append($('<p>').html('<strong>Type:</strong> ' + query.queryType));
    }
    
    // Assemble lightbox
    $lightbox.append($closeBtn);
    $lightbox.append($content);
    $overlay.append($lightbox);
    
    // Add to document and show
    $('body').append($overlay);
    
    // Close on overlay click
    $overlay.on('click', function(e) {
        if (e.target === $overlay[0]) {
            $overlay.remove();
        }
    });
    
    // Close on Escape key
    $(document).on('keydown.lightbox', function(e) {
        if (e.keyCode === 27) { // Escape key
            $overlay.remove();
            $(document).off('keydown.lightbox');
        }
    });
}

Añada estilo lightbox

Incluya estilos CSS para lightbox en la sección HTML <head> del 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>

Implementación mejorada del widget

Su widget mejorado completo con funcionalidad 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>

Consideraciones de accesibilidad: asegúrese de que la caja de luz sea accesible con el teclado e incluya etiquetas adecuadas para los lectores de pantalla. Pruebe con las características de accesibilidad integradas de Azure DevOps.

Importante

Rendimiento: las cajas de luz deben cargarse rápidamente. Considere la posibilidad de cargar los datos detallados solo cuando se abra la caja de luz, en lugar de obtener todo por adelantado.

Paso 5: Configurar el manifiesto de extensión

Registre tanto el widget configurable como su interfaz de configuración en el manifiesto de extensión.

Incorporación de contribuciones de widget y configuración

Actualice vss-extension.json para incluir dos nuevas contribuciones:

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

Requisitos para la contribución a la configuración

Propiedad Propósito Valor obligatorio
type Identifique la contribución como configuración del widget ms.vss-dashboards-web.widget-configuration
targets Dónde aparece la configuración ms.vss-dashboards-web.widget-configuration
uri Ruta de acceso al archivo HTML de configuración Su ruta del archivo de configuración

Patrón de destino de widgets

Para widgets configurables, la targets matriz debe incluir una referencia a la configuración:

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

Advertencia

Visibilidad del botón de configuración: si el widget no tiene como destino correctamente su contribución de configuración, el botón Configurar no aparece. Verifique que los nombres de publicador y de extensión coincidan exactamente con su manifiesto.

Paso 6: Empaquetar, publicar y compartir

Implemente la extensión mejorada con funcionalidades de configuración.

Si es su primera publicación, siga paso 6: Empaquetar, publicar y compartir. Para las extensiones existentes, vuelva a empaquetar y actualice directamente en el Marketplace.

Paso 7: Probar el widget configurable

Experimente el flujo de trabajo de configuración completo agregando y configurando el widget.

Añada el widget a su panel

  1. Ir a https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Vaya a Información general>Paneles.
  3. Selecciona Agregar un widget.
  4. Busque "Hello World Widget 3 (con configuración)" y seleccione Agregar.

Se muestra un mensaje de configuración, ya que el widget requiere configuración:

Captura de pantalla del panel Información general con un widget de ejemplo del catálogo.

Configuración del widget

Acceda a la configuración a través de cualquiera de los métodos:

  • Menú widget: mantenga el puntero sobre el widget, seleccione los puntos suspensivos (⋯) y, a continuación, Configurar.
  • Modo de edición del panel: seleccione Editar en el panel y, a continuación, el botón Configurar del widget.

El panel de configuración se abre con una vista previa en tiempo real en el centro. Seleccione una consulta en la lista desplegable para ver las actualizaciones inmediatas y, a continuación, seleccione Guardar para aplicar los cambios.

Paso 8: Agregar opciones de configuración avanzadas

Amplíe el widget con características de configuración más integradas, como nombres y tamaños personalizados.

Habilitación de la configuración de nombre y tamaño

Azure DevOps proporciona dos características configurables integradas:

Característica Propiedad de manifiesto Propósito
Nombres personalizados isNameConfigurable: true Los usuarios pueden invalidar el nombre del widget predeterminado
Varios tamaños Entradas múltiples supportedSizes Los usuarios pueden cambiar el tamaño de los widgets

Ejemplo de manifiesto mejorado

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

Mostrar nombres configurados

Para mostrar nombres de widget personalizados, actualice el widget para usar 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);
    }
}

Después de actualizar la extensión, puede configurar el nombre y el tamaño del widget:

Captura de pantalla que muestra dónde se puede configurar el nombre y el tamaño del widget.

Vuelva a empaquetar y actualice la extensión para habilitar estas opciones de configuración avanzadas.

¡Felicidades! Ha creado un widget de panel de Azure DevOps completo y configurable con funcionalidades de vista previa dinámica y opciones de personalización de usuario.