Agregar un widget de panel

Azure DevOps | Azure DevOps Server 2020 | Azure DevOps Server 2019

Los widgets de un panel se implementan como contribuciones en el marco de extensión. Una sola extensión puede tener varias contribuciones. Obtenga información sobre cómo crear una extensión con varios widgets como contribuciones.

Este artículo se divide en tres partes, cada una de ellas sobre la anterior, empezando por un widget simple y finalizando con un widget completo.

Sugerencia

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

Preparación y configuración necesaria para este tutorial

Para crear extensiones para Azure DevOps o TFS, hay algunos requisitos previos de software y herramientas que necesitará:

Conocimientos: Se requiere cierto conocimiento de JavaScript, HTML y CSS para el desarrollo de widget.

  • Una organización en Azure DevOps para instalar y probar el widget, puede encontrar más información aquí.
  • Editor de texto. Para muchos de los tutoriales, hemos usado Visual Studio Code , que se puede descargar Visual Studio Code
  • La versión más reciente del nodo, que se puede descargar aquí
  • CLI multiplataforma para Azure DevOps (tfx-cli) para empaquetar las extensiones.
    • tfx-cli se puede instalar mediante , un componente de Node.js mediante la ejecución de npm i -g tfx-cli
  • Un directorio principal para el proyecto. Este directorio se conoce como a lo home largo del tutorial.

Estructura de archivos de extensión:

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

Lo que encontrará en el tutorial

  1. En la primera parte de esta guía se muestra cómo crear un nuevo widget, que imprime un mensaje sencillo Hola mundo" .
  2. La segunda parte se basa en la primera agregando una llamada a una Azure DevOps API REST.
  3. En la tercera parte se explica cómo agregar la configuración al widget.

Nota:

Si tiene un poco de razón y quiere obtener el código de inmediato, puede descargar los ejemplos completos aquí. Una vez descargado, vaya a la carpeta y, a continuación, siga los pasos 6 y 7 directamente para publicar la extensión de ejemplo que tiene los tres widgets de ejemplo de distintas widgets complejidades. widgets

Introducción a algunos estilos básicos para widgets que se proporcionan de forma personalizada y algunas instrucciones sobre la estructura del widget.

Parte 1: Hola mundo

En esta parte se presenta un widget que imprime "Hola mundo" mediante JavaScript.

Panel de información general con un widget de ejemplo

Paso 1: Obtener el SDK de cliente: VSS.SDK.min.js

El script del SDK principal, VSS.SDK.min.js , permite que las extensiones web se comuniquen con el marco de Azure DevOps host. El script realiza operaciones como inicializar, notificar a la extensión que se carga o obtener contexto sobre la página actual. Obtenga el archivo del SDK VSS.SDK.min.js de cliente y agrégrélo a la aplicación web. Colóctelo en la home/sdk/scripts carpeta .

Use el comando "npm install" para recuperar el SDK:

npm install vss-web-extension-sdk

Para obtener más información sobre el SDK, visite la página de GitHub SDK de cliente.

Paso 2: La página HTML: hello-world.html

La página HTML es la que contiene el diseño e incluye referencias a CSS y JavaScript. Puede dar cualquier nombre a este archivo, simplemente asegúrese de actualizar todas las referencias hello-world a con el nombre que use.

El widget está basado en HTML y se hospeda en un iframe. Agregue el código HTML siguiente en hello-world.html . Agregamos la referencia obligatoria al archivo e incluyemos un elemento en , que se actualiza con la cadena VSS.SDK.min.js Hola mundo en el paso h2 siguiente.

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

Aunque se usa un archivo HTML, el marco omite la mayoría de los elementos principales HTML distintos del script y el vínculo.

Paso 3: JavaScript

Usamos JavaScript para representar contenido en el widget. En este artículo, encapsulamos todo el código JavaScript dentro de <script> un elemento en el archivo HTML. Puede elegir tener este código en un archivo JavaScript independiente y hacer referencia a él en el archivo HTML. El código representa el contenido. Este código de JavaScript también inicializa el SDK de VSS, asigna el código del widget al nombre del widget y notifica al marco de extensión los errores o aciertos del widget. En nuestro caso, a continuación se muestra el código que imprimiría "Hola mundo" en el widget. Agregue este script elemento al del código head 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 inicializa el protocolo de enlace entre el iframe que hospeda el widget y el marco de host. Se pasa explicitNotifyLoaded: true para que el widget pueda notificar explícitamente al host cuando hayamos terminado de cargar. Este control nos permite notificar la finalización de la carga después de garantizar que se cargan los módulos dependientes. Se pasa para que el widget Azure DevOps los estilos principales de los elementos usePlatformStyles: true HTML (como body, div, entre otros). Si el widget prefiere no usar estos estilos, puede pasar usePlatformStyles: false .

VSS.require se usa para cargar las bibliotecas de scripts de VSS necesarias. Una llamada a este método carga automáticamente bibliotecas generales como JQuery y JQueryUI. En nuestro caso, dependemos de la biblioteca WidgetHelpers, que se usa para comunicar el estado del widget al marco del widget. Por lo tanto, pasamos el nombre del módulo correspondiente TFS/Dashboards/WidgetHelpers y una devolución de llamada a VSS.require . Se llama a la devolución de llamada una vez cargado el módulo. La devolución de llamada tiene el resto del código JavaScript necesario para el widget. Al final de la devolución de llamada, llamamos a VSS.notifyLoadSucceeded para notificar la finalización de la carga.

WidgetHelpers.IncludeWidgetStyles incluye una hoja de estilos con algunos WidgetHelpers.IncludeWidgetStyles para empezar a trabajar. Asegúrese de encapsular el contenido dentro de un elemento HTML con la clase widget para usar estos estilos.

VSS.register se usa para asignar una función en JavaScript, que identifica de forma única el widget entre las diferentes contribuciones de la extensión. El nombre debe coincidir con id el que identifica la contribución, tal como se describe en el paso id En el caso de los widgets, la función que se pasa a debe devolver un objeto que cumpla el contrato; por ejemplo, el objeto devuelto debe tener una propiedad de carga cuyo valor es otra función que tenga la lógica básica para representar el VSS.registerIWidget widget. En nuestro caso, es actualizar el texto del elemento h2 a "Hola mundo". Es esta función a la que se llama cuando el marco del widget crea una instancia del widget. Usamos el WidgetStatusHelper de WidgetHelpers para devolver como WidgetStatus correcto.

Advertencia

Si el nombre usado para registrar el widget no coincide con el identificador de la contribución en el manifiesto, el widget funciona inesperadamente.

siempre vss-extension.json debe estar en la raíz de la carpeta (en esta guía, HelloWorld ). Para todos los demás archivos, puede colocarlos en la estructura que desee dentro de la carpeta, simplemente asegúrese de actualizar las referencias correctamente en los archivos HTML y en el vss-extension.json manifiesto.

Paso 4: Logotipo de la extensión: logo.png

El logotipo se muestra en Marketplace y en el catálogo de widgets una vez que un usuario instala la extensión.

Necesita un icono de catálogo de 98 px x 98 px. Elija una imagen, así como el logo.png nombre y colóctela en la img carpeta .

Para admitir TFS 2015 Update 3, necesita una imagen adicional de 330 px x 160 px. Esta imagen de vista previa se muestra en este catálogo. Elija una imagen, así móntela preview.png y colóctela en la img carpeta como antes.

Puede denominar estas imágenes como desee siempre que el manifiesto de extensión del paso siguiente se actualice con los nombres que use.

Paso 5: Manifiesto de la extensión: vss-extension.json

Cree un archivo JSON ( vss-extension.json , por ejemplo) en el home directorio con el siguiente contenido:

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

Para obtener más información sobre los atributos necesarios, vea la referencia del manifiesto de extensión.

Nota:

El publicador debe cambiarse aquí por el nombre del publicador. Para crear un publicador ahora, visite Package/Publish/Install.

Iconos

El icono sin especifica la ruta de acceso al icono de la extensión en el manifiesto.

Contribuciones

Cada entrada de contribución define las propiedades.

  • Identificador para identificar la contribución. Este identificador debe ser único dentro de una extensión. Este identificador debe coincidir con el nombre que usó en el paso 3 para registrar el widget.
  • Tipo de contribución. Para todos los widgets, el tipo debe ser ms.vss-dashboards-web.widget .
  • Matriz de destinos a los que contribuye la contribución. Para todos los widgets, el destino debe ser [ms.vss-dashboards-web.widget-catalog] .
  • Las propiedades son objetos que incluyen propiedades para el tipo de contribución. En el caso de los widgets, las siguientes propiedades son obligatorias.
Propiedad Descripción
name Nombre del widget que se mostrará en el catálogo de widgets.
description Descripción del widget que se mostrará en el catálogo de widgets.
catalogIconUrl Ruta de acceso relativa del icono de catálogo que agregó en el paso 4 para mostrarlo en el catálogo de widgets. La imagen debe ser de 98 px x 98 px. Si ha usado una estructura de carpetas diferente o un nombre de archivo diferente, especifique aquí la ruta de acceso relativa adecuada.
previewImageUrl Ruta de acceso relativa de la imagen de vista previa que agregó en el paso 4 para mostrarse solo en el catálogo de widgets para TFS 2015 Update 3. La imagen debe ser de 330 px x 160 px. Si ha usado una estructura de carpetas diferente o un nombre de archivo diferente, especifique aquí la ruta de acceso relativa adecuada.
uri Ruta de acceso relativa del archivo HTML que agregó en el paso 1. Si ha usado una estructura de carpetas diferente o un nombre de archivo diferente, especifique aquí la ruta de acceso relativa adecuada.
supportedSizes Matriz de tamaños admitidos por el widget. Cuando un widget admite varios tamaños, el primer tamaño de la matriz es el tamaño predeterminado del widget. se widget size especifica para las filas y columnas ocupadas por el widget en la cuadrícula del panel. Una fila o columna corresponde a 160 px. Cualquier dimensión por encima de 1x1 obtiene 10 px adicionales que representan el medianía entre widgets. Por ejemplo, un widget 3x2 es 160*3+10*2 ancho y 160*2+10*1 alto. El tamaño máximo admitido es 4x4 .
supportedScopes Por el momento, solo se admiten paneles de equipo. El valor debe ser project_team . En el futuro, cuando admitamos otros ámbitos de panel, habrá más opciones para elegir aquí.

Archivos

Los archivos indica los archivos que desea incluir en el paquete: la página HTML, los scripts, el script del SDK y el logotipo. Establezca addressable en a menos que incluya otros archivos que no true necesiten direcciones URL.

Nota:

Para obtener más información sobre el archivo de manifiesto de extensión,como sus propiedades y lo que hacen, consulte la referencia del manifiesto de extensión.

Paso 6: Empaquetar, publicar y compartir

Una vez que haya escrito la extensión, el siguiente paso para obtenerla en Marketplace es empaquetar todos los archivos juntos. Todas las extensiones se empaquetan como archivos .vsix compatibles con VSIX 2.0: Microsoft proporciona una interfaz de línea de comandos (CLI) multiplataforma para empaquetar la extensión.

Obtener la herramienta de empaquetado

Puede instalar o actualizar la CLI multiplataforma para Azure DevOps (tfx-cli) mediante , un componente de npmnpm, desde la línea de comandos.

npm i -g tfx-cli

Empaquetado de la extensión

Empaquetar la extensión en un archivo .vsix es sencillo una vez que tenga tfx-cli. Vaya al directorio principal de la extensión y ejecute el siguiente comando.

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

Nota:

La versión de una extensión o integración debe incrementarse en cada actualización.
Al actualizar una extensión existente, actualice la versión en el manifiesto o pase el modificador --rev-version de línea de comandos. Esto incrementa el número de versión de revisión de la extensión y guarda la nueva versión en el manifiesto.

Una vez que tenga la extensión empaquetada en un archivo .vsix, estará listo para publicar la extensión en Marketplace.

Creación de un publicador para la extensión

Todas las extensiones, incluidas las extensiones de Microsoft, se identifican como proporcionadas por un publicador. Si aún no es miembro de un publicador existente, creará uno.

  1. Inicie sesión en el portal Visual Studio publicación de Marketplace
  2. Si aún no es miembro de un publicador existente, se le pedirá que cree un publicador. Si no se le pide que cree un publicador, desplácese hacia abajo hasta la parte inferior de la página y seleccione Publicar extensiones debajo de Sitios relacionados.
    • Especifique un identificador para el publicador, por ejemplo: mycompany-myteam
      • El identificador se usa como valor del publisher atributo en el archivo de manifiesto de las extensiones.
    • Especifique un nombre para mostrar para el publicador, por ejemplo: My Team
  3. Revise el contrato de Publisher Marketplace y seleccione Crear.

Ahora se ha definido el publicador. En una versión futura, puede conceder permisos para ver y administrar las extensiones del publicador. Es fácil y seguro que los equipos y las organizaciones publiquen extensiones en un publicador común, pero sin necesidad de compartir un conjunto de credenciales entre un conjunto de usuarios.

Actualice el archivo de manifiesto en los ejemplos para reemplazar el identificador ficticio del publicador fabrikam por el identificador del publicador.

Publicar y compartir la extensión

Después de crear un publicador, ahora puede cargar la extensión en Marketplace.

  1. Busque el Upload nuevo botón de extensión, vaya al archivo .vsix empaquetado y seleccione Cargar.

También puede cargar la extensión a través de la línea de comandos mediante el comando en lugar de empaquetar y tfx extension publish publicar la extensión en un tfx extension create paso. Opcionalmente, puede usar --share-with para compartir la extensión con una o varias cuentas después de la publicación. También necesitará un token de acceso personal.

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

Paso 7: Agregar widget desde el catálogo

  1. Vaya al proyecto en Azure DevOps,http://dev.azure.com/{yourOrganization}/{yourProject}

  2. Seleccione Informacióngeneral y, a continuación, paneles.

  3. Elija Agregar un widget.

  4. Resalte el widget y, a continuación, seleccione Agregar.

    El widget aparece en el panel.

Parte 2: Hola mundo con Azure DevOps API REST

Los widgets pueden llamar a cualquiera de las API REST Azure DevOps para interactuar con Azure DevOps recursos. En este ejemplo, usamos la API REST para WorkItemTracking para capturar información sobre una consulta existente y mostrar información de consulta en el widget justo debajo del texto "Hola mundo".

Panel de información general con un widget de ejemplo mediante la API REST para WorkItemTracking.

Paso 1: HTML

Copie el archivo hello-world.html del ejemplo anterior y cambie el nombre de la copia a hello-world2.html . La carpeta ahora tiene el siguiente aspecto:

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

Agregue un nuevo div elemento justo debajo de h2 para contener la información de consulta. Actualice el nombre del widget de HelloWorldWidget a en la línea donde se llama a HelloWorldWidget2VSS.register . Esto permite que el marco identifique de forma única el widget dentro de la extensión.
<!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>

Paso 2: Acceso a Azure DevOps recursos

Para habilitar el acceso a Azure DevOps recursos, los ámbitos deben especificarse en el manifiesto de extensión. Agregamos el vso.work ámbito a nuestro manifiesto.
Este ámbito indica que el widget necesita acceso de solo lectura a las consultas y elementos de trabajo. Vea todos los ámbitos disponibles aquí. Agregue lo siguiente al final del manifiesto de extensión.

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

Advertencia

Actualmente no se admite la adición o el cambio de ámbitos después de publicar una extensión. Si ya ha cargado la extensión, quítela de Marketplace. Vaya a Visual Studio Portal de publicación de Marketplace,haga clic con el botón derecho en la extensión y seleccione "Quitar".

Paso 3: Realizar la llamada a la API rest

Hay muchas bibliotecas del lado cliente a las que se puede acceder a través del SDK para realizar llamadas a la API rest en Azure DevOps. Estas bibliotecas se denominan clientes REST y son contenedores de JavaScript en torno a llamadas Ajax para todos los puntos de conexión del lado servidor disponibles. Puede usar los métodos proporcionados por estos clientes en lugar de escribir llamadas Ajax usted mismo. Estos métodos asignan las respuestas de api a objetos que el código puede consumir.

En este paso, se actualiza la VSS.require llamada a para cargar , que proporciona el cliente REST TFS/WorkItemTracking/RestClient WorkItemTracking. Podemos usar este cliente REST para obtener información sobre una consulta denominada Feedback en la carpeta Shared Queries .

Dentro de la función que se pasa VSS.register a , creamos una variable para contener el identificador de proyecto actual. Necesitamos esta variable para capturar la consulta. También creamos un nuevo método getQueryInfo para usar el cliente REST. Método al que se llama a continuación desde el método de carga.

El método getClient proporciona una instancia del cliente REST que necesitamos. El método getQuery devuelve la consulta encapsulada en una promesa. El aspecto VSS.require actualizado es el siguiente:

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

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

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

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

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

Observe el uso del método Failure de WidgetStatusHelper . Permite indicar al marco de widget que se ha producido un error y aprovechar la experiencia de error estándar proporcionada a todos los widgets.

Si no tiene la consulta en la carpeta , reemplace en el código por la ruta de acceso de una consulta Feedback que existe en el Shared QueriesShared Queries\Feedback proyecto.

Paso 4: Mostrar la respuesta

El último paso es representar la información de consulta dentro del widget. La getQuery función devuelve un objeto de tipo dentro de una Contracts.QueryHierarchyItem promesa. En este ejemplo, se muestra el identificador de consulta, el nombre de la consulta y el nombre del creador de la consulta en el texto "Hola mundo". Reemplace el // Do something with the query comentario por el siguiente:

    // 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 final hello-world2.html es la siguiente:

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

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

                    var getQueryInfo = function (widgetSettings) {
                        // Get a WIT client to make REST calls to Azure DevOps Services
                        return TFS_Wit_WebApi.getClient().getQuery(projectId, "Shared Queries/Feedback")
                            .then(function (query) {
                                // Create a list with query details                                
                                var $list = $('<ul>');
                                $list.append($('<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: Actualizaciones del manifiesto de extensión

En este paso, actualizamos el manifiesto de extensión para incluir una entrada para nuestro segundo widget. Agregue una nueva contribución a la matriz en la propiedad y agregue el nuevo archivo a contributionshello-world2.html la matriz en la propiedad files. Necesita otra imagen de vista previa para el segundo widget. Así preview2.png mismo, así mismo, colóctelo en la img carpeta .

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

Paso 6: Empaquetar, publicar y compartir

Empaquete, publique y comparta la extensión. Si ya ha publicado la extensión, puede volver a empaquetar la extensión, como se describe aquí,y actualizarla directamente a Marketplace.

Paso 7: Agregar widget desde el catálogo

Ahora, vaya al panel del equipo en https:\//dev.azure.com/{yourOrganization}/{yourProject} . Si esta página ya está abierta, actual ábrala. Mantenga el puntero sobre el botón Editar en la parte inferior derecha y seleccione el botón Agregar. Se abre el catálogo de widgets donde encontrará el widget que ha instalado. Elija el widget y seleccione el botón "Agregar" para agregarlo al panel.

Parte 3: Hola mundo con configuración

En la parte 2 de esta guía, ha visto cómo crear un widget que muestra información de consulta para una consulta codificada de forma manual. En esta parte, se agrega la capacidad de configurar la consulta que se va a usar en lugar de la codificada de forma hard-code. Cuando está en modo de configuración, el usuario puede ver una vista previa en directo del widget en función de sus cambios. Estos cambios se guardan en el widget en el panel cuando el usuario selecciona Guardar.

Vista previa dinámica del panel de información general del widget en función de los cambios.

Paso 1: HTML

Las implementaciones de widgets y configuraciones de widget son muy similares. Ambos se implementan en el marco de extensión como contribuciones. Ambos usan el mismo archivo SDK, VSS.SDK.min.js . Ambos se basan en HTML, JavaScript y CSS.

Copie el archivo html-world2.html del ejemplo anterior y cambie el nombre de la copia a hello-world3.html . Agregue otro archivo HTML denominado configuration.html . La carpeta ahora es similar al ejemplo siguiente:

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

Agregue el código HTML siguiente en configuration.html . Básicamente, agregamos la referencia obligatoria al archivo y un elemento para que la lista desplegable VSS.SDK.min.js seleccione una consulta de una lista select preestablecida.
    <!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: JavaScript: configuración

Use JavaScript para representar contenido en la configuración del widget como hicimos para el widget en el paso 3 de la parte 1 de esta guía. Este código de JavaScript representa el contenido, inicializa el SDK de VSS, asigna el código de la configuración del widget al nombre de configuración y pasa los valores de configuración al marco. En nuestro caso, a continuación se muestra el código que carga la configuración del widget. Abra el archivo configuration.html y el elemento siguiente en <script><head> .

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

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

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

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

VSS.init, VSS.require y desempeñan el mismo rol que VSS.register desempeñaban para el widget como se describe en VSS.init La única diferencia es que para las configuraciones de widget, la función que se pasa a debe devolver VSS.register un objeto que cumpla el IWidgetConfiguration contrato.

La load propiedad del contrato debe tener una función como su IWidgetConfiguration valor. Esta función tiene el conjunto de pasos para representar la configuración del widget. En nuestro caso, es para actualizar el valor seleccionado del elemento desplegable con la configuración existente, si existe. Se llama a esta función cuando el marco crea una instancia de widget configuration

La onSave propiedad del contrato debe tener una función como su IWidgetConfiguration valor. El marco llama a esta función cuando el usuario selecciona Guardar en el panel de configuración. Si la entrada del usuario está lista para guardarse, deserialice en una cadena, forme el objeto y custom settings use para guardar la entrada del WidgetConfigurationSave.Valid() usuario.

En esta guía, se usa JSON para serializar la entrada del usuario en una cadena. Puede elegir cualquier otra manera de serializar la entrada del usuario en la cadena. Es accesible para el widget a través de la propiedad customSettings del WidgetSettings objeto . El widget tiene que deserializarlo, que se trata en el paso 4.

Paso 3: JavaScript: Habilitar Live Preview

Para habilitar la actualización de vista previa activa cuando el usuario selecciona una consulta en la lista desplegable, adjuntamos un controlador de eventos de cambio al botón. Este controlador notifica al marco que la configuración ha cambiado. También pasa el que customSettings se va a usar para actualizar la versión preliminar. Para notificar al marco de trabajo, es necesario llamar al método notifywidgetConfigurationContext en . Toma dos parámetros, el nombre del evento, que en este caso es , y un objeto para el evento, creado a partir de con la ayuda WidgetHelpers.WidgetEvent.ConfigurationChangeEventArgs del método customSettingsWidgetEvent.Args auxiliar.

Agregue lo siguiente en la función asignada a la load propiedad .

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

Debe notificar al marco de trabajo el cambio de configuración al menos una vez para que se pueda habilitar el botón "Guardar".

Al final, el configuration.html tiene el siguiente 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("TFS/Dashboards/WidgetHelpers", function (WidgetHelpers) {
                    VSS.register("HelloWorldWidget.Configuration", function () {   
                        var $queryDropdown = $("#query-path-dropdown");

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

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

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

Paso 4: JavaScript: implementar la recarga en el widget

Hemos configurado la configuración del widget para almacenar la ruta de acceso de consulta seleccionada por el usuario. Ahora tenemos que actualizar el código del widget para usar esta configuración almacenada en lugar de la codificada de forma segura Shared Queries/Feedback del ejemplo anterior.

Abra el archivo hello-world3.html y actualice el nombre del widget de a en la línea donde se llama a HelloWorldWidget2HelloWorldWidget3VSS.register . Esto permite que el marco identifique de forma única el widget dentro de la extensión.

La función asignada a HelloWorldWidget3 a través de devuelve actualmente un objeto que satisface el VSS.registerIWidget contrato. Puesto que nuestro widget ahora necesita configuración, esta función debe actualizarse para devolver un objeto que cumpla el IConfigurableWidget contrato. Para ello, actualice la instrucción return para incluir una propiedad denominada reload como se indica a continuación. El valor de esta propiedad es una función que llama al getQueryInfo método una vez más. El marco llama a este método de recarga cada vez que cambia la entrada del usuario para mostrar la versión preliminar en directo. También se llama a este método cuando se guarda la configuración.

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

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

La ruta de acceso de consulta codificada de forma fuerte en debe reemplazarse por la ruta de acceso de consulta configurada, que se puede extraer del parámetro que getQueryInfo se pasa al método widgetSettings . Agregue lo siguiente al principio del método y reemplace la ruta de consulta codificada de getQueryInfo forma fuerte por 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();
}

En este momento, el widget está listo para representarse con la configuración configurada.

Las propiedades load y tienen una función reload similar. Este es el caso de la mayoría de los widgets simples. En el caso de los widgets complejos, habría ciertas operaciones que le gustaría ejecutar una sola vez, independientemente de cuántas veces cambie la configuración. O bien, puede haber algunas operaciones de peso pesado que no necesiten ejecutarse más de una vez. Estas operaciones formarán parte de la función correspondiente a la load propiedad y no a la propiedad reload .

Paso 5: Actualizaciones del manifiesto de extensión

Abra el vss-extension.json archivo para incluir dos entradas nuevas en la matriz en la propiedad contributions . Uno para el HelloWorldWidget3 widget y el otro para su configuración. Necesita otra imagen de vista previa para el tercer widget. Así preview3.png mismo, así mismo colóctelo en la img carpeta . Actualice la matriz en la propiedad para incluir los dos nuevos archivos files HTML que hemos agregado en este ejemplo.

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

Tenga en cuenta que la contribución para la configuración del widget sigue un modelo ligeramente diferente al del propio widget. Una entrada de contribución para la configuración del widget tiene:
  • Identificador para identificar la contribución. Debe ser único dentro de una extensión.
  • Tipo de contribución. Para todas las configuraciones de widget, debe ser ms.vss-dashboards-web.widget-configuration
  • Matriz de destinos a los que contribuye la contribución. Para todas las configuraciones de widget, tiene una sola entrada: ms.vss-dashboards-web.widget-configuration .
  • Las propiedades que contienen un conjunto de propiedades que incluyen el nombre, la descripción y el URI del archivo HTML usado para la configuración.

Para admitir la configuración, también es necesario cambiar la contribución del widget. La matriz de destinos del widget debe actualizarse para incluir el identificador de la configuración con el formato . , que publisher> en <id for the extension><id for the configuration contribution> este caso es fabrikam.vsts-extensions-myExtensions.HelloWorldWidget.Configuration .

Advertencia

Si la entrada de contribución del widget configurable no tiene como destino la configuración con el publicador y el nombre de extensión correctos como se describió anteriormente, el botón Configurar no se muestra para el widget.

Al final de esta parte, el archivo de manifiesto debe contener tres widgets y una configuración. Puede obtener el manifiesto completo del ejemplo aquí.

Paso 6: Empaquetar, publicar y compartir

Si aún no ha publicado la extensión, lea esta sección para empaquetar, publicar y compartir la extensión. Si ya ha publicado la extensión antes de este punto, puede volver a empaquetar la extensión como se describe aquí y actualizarla directamente a Marketplace.

Paso 7: Agregar widget desde el catálogo

Ahora, vaya al panel del equipo en https://dev.azure.com/{yourOrganization}/{yourProject}. Si esta página ya está abierta, actual ábrala. Mantenga el puntero sobre el botón Editar en la parte inferior derecha y seleccione el botón Agregar. Esto debería abrir el catálogo de widgets en el que se encuentra el widget que ha instalado. Elija el widget y seleccione el botón "Agregar" para agregarlo al panel.

Verá un mensaje que le pide que configure el widget.

Panel de información general con un widget de ejemplo del catálogo.

Hay dos maneras de configurar widgets. Una es mantener el puntero sobre el widget, seleccionar los puntos suspensivos que aparecen en la esquina superior derecha y, a continuación, seleccionar Configurar. La otra es seleccionar el botón Editar en la parte inferior derecha del panel y, a continuación, seleccionar el botón Configurar que aparece en la esquina superior derecha del widget. Se abre la experiencia de configuración en el lado derecho y una vista previa del widget en el centro. Vaya y elija una consulta en la lista desplegable. La vista previa en directo muestra los resultados actualizados. Seleccione "Guardar" y el widget mostrará los resultados actualizados.

Paso 8: Configurar más (opcional)

Puede agregar tantos elementos de formulario HTML como necesite en para configuration.html una configuración adicional. Hay dos características configurables que están disponibles de forma estándar: nombre del widget y tamaño del widget.

De forma predeterminada, el nombre que proporcione para el widget en el manifiesto de extensión se almacena como el nombre del widget para cada instancia del widget que se agrega a un panel. Puede permitir que los usuarios configuren esto, para que puedan agregar cualquier nombre que quieran a su instancia del widget. Para permitir esta configuración, agregue isNameConfigurable:true en la sección de propiedades del widget en el manifiesto de extensión.

Si proporciona más de una entrada para el widget en la matriz en el manifiesto de extensión, los usuarios también pueden configurar el tamaño supportedSizes del widget.

El manifiesto de extensión del tercer ejemplo de esta guía sería parecido al siguiente si habilitamos la configuración de tamaño y nombre del widget:

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

Con el cambio anterior, vuelva a empaquetary actualice la extensión. Actualice el panel que tiene este widget (Hola mundo Widget 3 (con config)). Abra el modo de configuración del widget; ahora debería poder ver la opción para cambiar el nombre y el tamaño del widget.

Widget donde se pueden configurar el nombre y el tamaño

Vaya y elija un tamaño diferente al de la lista desplegable. Verá que se cambia el tamaño de la versión preliminar en directo. Guarde el cambio y también se cambiará el tamaño del widget en el panel.

Advertencia

Si quita un tamaño ya compatible, el widget no se puede cargar correctamente. Estamos trabajando en una corrección para una versión futura.

Cambiar el nombre del widget no da lugar a ningún cambio visible en el widget. Esto se debe a que nuestros widgets de ejemplo no muestran el nombre del widget en ningún lugar. Vamos a modificar el código de ejemplo para mostrar el nombre del widget en lugar del texto codificado de forma Hola mundo".

Para ello, reemplace el texto codificado de forma Hola mundo por en la línea donde se establece widgetSettings.name el texto del h2 elemento. Esto garantiza que el nombre del widget se muestra cada vez que el widget se carga al actualizar la página. Puesto que queremos que la versión preliminar en directo se actualice cada vez que cambie la configuración, también debemos agregar el mismo código en reload la parte del código. La instrucción return final de hello-world3.html es la siguiente:

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

Vuelva a empaquetary actualice la extensión de nuevo. Actualice el panel que tiene este widget. Cualquier cambio en el nombre del widget, en el modo de configuración, actualice el título del widget ahora.