Agregar un widget de panel
Azure DevOps Services | Azure DevOps Server 2022 | 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. Aprenda a crear una extensión con varios widgets como contribuciones.
Este artículo se divide en tres partes, cada uno de los cuales se basa en el anterior, empezando por un widget simple y terminando con un widget completo.
Sugerencia
Consulte nuestra documentación más reciente sobre el desarrollo de extensiones mediante el SDK de extensión de Azure DevOps.
Preparación y configuración necesaria para este tutorial
Para crear extensiones para Azure DevOps o TFS, hay algunas herramientas y software de requisitos previos que necesitará:
Conocimiento: Se requiere cierto conocimiento de JavaScript, HTML y CSS para el desarrollo de widgets.
- Una organización de Azure DevOps para instalar y probar el widget, puede encontrar más información aquí.
- Un editor de texto. Para muchos de los tutoriales, usamos
Visual Studio Code
, que se puede descargar aquí. - 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
npm
, un componente de Node.js mediante la ejecución denpm i -g tfx-cli
- tfx-cli se puede instalar mediante
- Un directorio principal para el proyecto. Este directorio se conoce como
home
a lo 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
- En la primera parte de esta guía se muestra cómo crear un widget, que imprime un mensaje simple de "Hola mundo".
- La segunda parte se basa en la primera agregando una llamada a una API REST de Azure DevOps.
- En la tercera parte se explica cómo agregar configuración al widget.
Nota
Si tiene prisa y desea poner las manos en el código de inmediato, puede descargar los ejemplos aquí.
Una vez descargado, vaya a la widgets
carpeta y, a continuación, siga el paso 6 y el paso 7 directamente para publicar la extensión de ejemplo que tiene los tres widgets de ejemplo de distintas complejidades.
Introducción a algunos estilos básicos para widgets que proporcionamos de forma predeterminada y algunas instrucciones sobre la estructura de widgets.
Parte 1: Hola mundo
Esta parte presenta un widget que imprime "Hola mundo" mediante JavaScript.
Paso 1: Obtener el SDK de cliente: VSS.SDK.min.js
El script principal del SDK, VSS.SDK.min.js
, permite que las extensiones web se comuniquen con el marco de Azure DevOps del host. El script realiza operaciones como inicializar, notificar la extensión se carga o obtener contexto sobre la página actual.
Obtenga el archivo del SDK de cliente VSS.SDK.min.js
y agréguelo a la aplicación web. Colóquelo en la home/sdk/scripts
carpeta .
Use el comando "npm install" para recuperar el SDK:
npm install vss-web-extension-sdk
Para más información sobre el SDK, visite la página de GitHub del SDK de cliente.
Paso 2: Página HTML: hello-world.html
La página HTML es el pegamento que contiene el diseño juntos e incluye referencias a CSS y JavaScript.
Puede asignar un nombre a este archivo cualquier cosa, solo tiene que asegurarse de actualizar todas las referencias a con hello-world
el nombre que use.
El widget se basa en HTML y se hospeda en un iframe.
Agregue el código HTML siguiente en hello-world.html
. Agregamos la referencia obligatoria al VSS.SDK.min.js
archivo e incluyemos un h2
elemento en , que se actualiza con la cadena Hola mundo en el paso 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, la mayoría de los elementos principales HTML distintos del script y el vínculo se omiten en el marco de trabajo.
Paso 3: JavaScript
Usamos JavaScript para representar contenido en el widget. En este artículo, encapsulamos todo nuestro código JavaScript dentro de un <script>
elemento del 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 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 de éxitos o errores 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 en del head
código 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.
explicitNotifyLoaded: true
Pasamos 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 asegurarse de que se cargan los módulos dependientes.
usePlatformStyles: true
Pasamos para que el Widget pueda usar los estilos principales de Azure DevOps para elementos html (como body, div, etc.). Si el widget prefiere no usar estos estilos, pueden 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 de widgets.
Por lo tanto, pasamos el nombre TFS/Dashboards/WidgetHelpers
del módulo correspondiente 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 VSS.notifyLoadSucceeded
a para notificar la finalización de la carga.
WidgetHelpers.IncludeWidgetStyles
incluye una hoja de estilos con algunos css básicos para empezar. Asegúrese de encapsular el contenido dentro de un elemento HTML con 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 el id
que identifica su contribución como se describe en el paso 5. En el caso de los widgets, la función que se pasa a VSS.register
debe devolver un objeto que cumpla el IWidget
contrato, por ejemplo, el objeto devuelto debe tener una propiedad de carga cuyo valor es otra función que tiene la lógica principal para representar el widget.
En nuestro caso, es actualizar el texto del h2
elemento a "Hola mundo".
Se trata de esta función a la que se llama cuando el marco de widget crea una instancia del widget.
WidgetStatusHelper
Usamos desde 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.
vss-extension.json
siempre 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, solo tiene que asegurarse 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ígnela logo.png
el nombre y colóquela 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ígnela preview.png
el nombre y colóquela en la img
carpeta como antes.
Sin embargo, puede asignar un nombre a estas imágenes 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
- Cada extensión debe tener un archivo de manifiesto de extensión
- Leer la referencia del manifiesto de extensión
- Obtenga más información sobre los puntos de contribución en Puntos de extensibilidad.
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, consulte la referencia del manifiesto de extensión.
Nota
El publicador aquí debe cambiarse al nombre del publicador. Para crear un publicador ahora, visite Package/Publish/Install.
Iconos
La estrofa de iconos 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 su 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 va a mostrar en el catálogo de widgets. |
description | Descripción del widget que se va a 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 la ruta de acceso relativa adecuada aquí. |
previewImageUrl | Ruta de acceso relativa de la imagen de vista previa que agregó en el paso 4 para mostrarse en el catálogo de widgets solo 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 la ruta de acceso relativa adecuada aquí. |
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 la ruta de acceso relativa adecuada aquí. |
supportedSizes | Matriz de tamaños compatibles con el widget. Cuando un widget admite varios tamaños, el primer tamaño de la matriz es el tamaño predeterminado del widget. widget size se 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 superior a 1x1 obtiene un adicional de 10 px que representa el medianil entre widgets. Por ejemplo, un widget de 3x2 es 160*3+10*2 ancho y 160*2+10*1 alto. El tamaño máximo admitido es 4x4 . |
supportedScopes | En este momento, solo se admiten paneles de equipo. El valor debe ser project_team . En el futuro, cuando se admitan otros ámbitos de panel, habrá más opciones para elegir aquí. |
Archivos
La estrofa de archivos indica los archivos que desea incluir en el paquete: la página HTML, los scripts, el script del SDK y el logotipo.
Establézcalo addressable
en a true
menos que incluya otros archivos que no necesiten ser direccionables por 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 entrar 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 multiplataforma (CLI) para empaquetar la extensión.
Obtención de la herramienta de empaquetado
Puede instalar o actualizar la CLI multiplataforma para Azure DevOps (tfx-cli) mediante npm
, un componente de Node.js, 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 sin esfuerzo una vez que tenga el 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 de --rev-version
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 haya empaquetado la extensión 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.
- Inicio de sesión en el Portal de publicación de Visual Studio Marketplace
- 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 para el atributo en el
publisher
archivo de manifiesto de las extensiones.
- El identificador se usa como valor para el atributo en el
- Especifique un nombre para mostrar para el publicador, por ejemplo:
My Team
- Especifique un identificador para el publicador, por ejemplo:
- Revise el Contrato de publicador de Marketplace y seleccione Crear.
Ahora se define el publicador. En una versión futura, puede conceder permisos para ver y administrar las extensiones del publicador. Es fácil y más seguro que los equipos y las organizaciones publiquen extensiones en un publicador común, pero sin necesidad de compartir un conjunto de credenciales en un conjunto de usuarios.
Actualice el archivo de vss-extension.json
manifiesto de los ejemplos para reemplazar el identificador fabrikam
de publicador ficticio por el identificador del publicador.
Publicación y uso compartido de la extensión
Después de crear un publicador, ahora puede cargar la extensión en Marketplace.
- Busque el botón Cargar nueva 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 tfx extension publish
comando en lugar de tfx extension create
empaquetar y publicar la extensión en un 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
Vaya al proyecto en Azure DevOps.
http://dev.azure.com/{Your_Organization}/{Your_Project}
Seleccione Información general y, después, Paneles.
Elija Agregar un widget.
Resalte el widget y, a continuación, seleccione Agregar.
El widget aparece en el panel.
Parte 2: Hola mundo con la API REST de Azure DevOps
Los widgets pueden llamar a cualquiera de las API REST de Azure DevOps para interactuar con los recursos de Azure DevOps. 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".
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 es similar a la siguiente:
|--- 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 elemento "div" justo debajo de "h2" para contener la información de consulta. Actualice el nombre del widget de "HelloWorldWidget" a "HelloWorldWidget2" en la línea donde se llama a "VSS.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 los recursos de Azure DevOps
Para habilitar el acceso a los recursos de Azure DevOps, los ámbitos deben especificarse en el manifiesto de extensión. Agregamos el vso.work
ámbito al manifiesto.
Este ámbito indica que el widget necesita acceso de solo lectura a las consultas y elementos de trabajo. Consulte 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 modificación de ámbitos después de publicar una extensión. Si ya ha cargado la extensión, quítela de Marketplace. Vaya al , 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 las llamadas de Ajax para todos los puntos de conexión del lado servidor disponibles. Puede usar métodos proporcionados por estos clientes en lugar de escribir llamadas Ajax usted mismo. Estos métodos asignan las respuestas de la API a objetos que el código puede consumir.
En este paso, se actualiza la VSS.require
llamada a para cargar TFS/WorkItemTracking/RestClient
, que proporciona el cliente REST 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 pasamos a VSS.register
, creamos una variable para contener el identificador del proyecto actual. Necesitamos esta variable para capturar la consulta.
También se crea un nuevo método getQueryInfo
para usar el cliente REST. A continuación, se llama a este método desde el método load.
El método getClient
proporciona una instancia del cliente REST que necesitamos.
El método getQuery
devuelve la consulta ajustada en una promesa.
La actualización VSS.require
tiene el siguiente aspecto:
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
Feedback
consulta en laShared Queries
carpeta , reemplaceShared Queries\Feedback
en el código por la ruta de acceso de una consulta que existe en el 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 Contracts.QueryHierarchyItem
dentro de una 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 lo siguiente:
// Create a list with query details
var $list = $('<ul>');
$list.append($('- ').text("Query Id: " + query.id));
$list.append($('- ').text("Query Name: " + query.name));
$list.append($('- ').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($('- ').text("Query ID: " + query.id));
$list.append($('- ').text("Query Name: " + query.name));
$list.append($('- ').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: Novedades de manifiesto de extensión
En este paso, actualizamos el manifiesto de extensión para incluir una entrada para el segundo widget.
Agregue una nueva contribución a la matriz en la contributions
propiedad y agregue el nuevo archivo hello-world2.html
a la matriz en la propiedad files.
Necesita otra imagen de vista previa para el segundo widget. Asígnelo preview2.png
y colóquelo 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 y actualizarla directamente a Marketplace.
Paso 7: Agregar widget desde el catálogo
Ahora, vaya al panel del equipo en https:\//dev.azure.com/{Your_Organization}/{Your_Project}
. Si esta página ya está abierta, actualízela.
Mantenga el puntero sobre el botón Editar de la parte inferior derecha y seleccione el botón Agregar. El catálogo de widgets se abre donde se encuentra el widget que instaló.
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 rígida. En esta parte, se agrega la capacidad de configurar la consulta que se va a usar en lugar de la codificada de forma rígida. Cuando está en modo de configuración, el usuario obtiene una vista previa activa del widget en función de sus cambios. Estos cambios se guardan en el widget del panel cuando el usuario selecciona Guardar.
Paso 1: HTML
Las implementaciones de widgets y configuraciones de widgets son mucho iguales. 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 a "VSS". SDK.min.js" archivo y un elemento "select" para la lista desplegable para seleccionar una consulta de una lista 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 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 las opciones 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 <script>
en .<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 VSS.register
desempeñan el mismo papel que desempeñaron para el widget, como se describe en la parte 1.
La única diferencia es que, para las configuraciones de widget, la función que se pasa a VSS.register
debe devolver un objeto que cumpla el IWidgetConfiguration
contrato.
La load
propiedad del IWidgetConfiguration
contrato debe tener una función como su valor.
Esta función tiene el conjunto de pasos para representar la configuración del widget.
En nuestro caso, es actualizar el valor seleccionado del elemento desplegable con la configuración existente si existe.
Se llama a esta función cuando se crea una instancia del marco widget configuration
La onSave
propiedad del IWidgetConfiguration
contrato debe tener una función como su 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 guardarla, serialícela en una cadena, forme el custom settings
objeto y use WidgetConfigurationSave.Valid()
para guardar la entrada del usuario.
En esta guía, usamos 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 deserializar esto, que se trata en el paso 4.
Paso 3: JavaScript: Habilitación de live preview
Para habilitar la actualización en 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 ha cambiado la configuración.
También pasa el customSettings
objeto que se va a usar para actualizar la versión preliminar. Para notificar al marco, es notify
necesario llamar al método en .widgetConfigurationContext
Toma dos parámetros, el nombre del evento, que en este caso es WidgetHelpers.WidgetEvent.ConfigurationChange
y un EventArgs
objeto para el evento, creado a partir customSettings
de con la ayuda del WidgetEvent.Args
método 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 del cambio de configuración al menos una vez para que se pueda habilitar el botón "Guardar".
Al final, configuration.html
tiene 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("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: implementación de 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 del código Shared Queries/Feedback
duro del ejemplo anterior.
Abra el archivo hello-world3.html
y actualice el nombre del widget de HelloWorldWidget2
a HelloWorldWidget3
en la línea donde se llama a VSS.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 VSS.register
de actualmente devuelve un objeto que satisface el IWidget
contrato.
Dado que el widget ahora necesita configuración, esta función debe actualizarse para devolver un objeto que satisfaga 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 vista previa activa. También se llama 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 rígida en "getQueryInfo" debe reemplazarse por la ruta de acceso de consulta configurada, que se puede extraer del parámetro "widgetSettings" que se pasa al método . Agregue lo siguiente al principio del método "getQueryInfo" y reemplace la ruta de consulta codificada de forma rígida 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 las opciones configuradas.
Tanto las
load
propiedades comoreload
tienen una función 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 solo una vez, independientemente de cuántas veces cambie la configuración. O puede haber algunas operaciones de gran peso que no necesiten ejecutarse más de una vez. Estas operaciones formarían parte de la función correspondiente a laload
propiedad y no a lareload
propiedad .
Paso 5: Novedades de manifiesto de extensión
Abra el vss-extension.json
archivo para incluir dos entradas nuevas en la matriz en la contributions
propiedad . Uno para el HelloWorldWidget3
widget y el otro para su configuración.
Necesita otra imagen de vista previa para el tercer widget. Asígnelo preview3.png
y colóquelo en la img
carpeta .
Actualice la matriz de la files
propiedad para incluir los dos nuevos archivos 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 propio widget. Una entrada de contribución para la configuración del widget tiene:
- Identificador para identificar su 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 única 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 en el formato <publisher
>.><<id for the configuration contribution
id for the extension
> En 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 mediante el nombre de extensión y el publicador correctos, como se ha descrito 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 y actualizarla directamente a Marketplace.
Paso 7: Agregar widget desde el catálogo
Ahora, vaya al panel del equipo en https://dev.azure.com/{Your_Organization}/{Your_Project}. Si esta página ya está abierta, actualízela. Mantenga el puntero sobre el botón Editar de la parte inferior derecha y seleccione el botón Agregar. Esto debería abrir el catálogo de widgets donde encuentre el widget que instaló. Elija el widget y seleccione el botón "Agregar" para agregarlo al panel.
Verá un mensaje que le pide que configure el widget.
Hay dos maneras de configurar widgets. Uno 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 consiste en 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. Abre la experiencia de configuración en el lado derecho y una vista previa del widget en el centro. Continúe 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 configuration.html
para la configuración adicional.
Hay dos características configurables que están disponibles de forma predeterminada: el nombre del widget y el 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 agregue a un panel.
Puede permitir que los usuarios lo configuren para que puedan agregar cualquier nombre que desee 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 supportedSizes
matriz en el manifiesto de extensión, los usuarios también pueden configurar el tamaño del widget.
El manifiesto de extensión del tercer ejemplo de esta guía sería similar al siguiente si habilitamos el nombre del widget y la configuración de tamaño:
{
...
"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 empaquetar y 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.
Adelante y elija un tamaño diferente en la lista desplegable. Verá que se cambia el tamaño de la versión preliminar en directo. Guarde el cambio y el widget en el panel también se cambie.
Advertencia
Si quita un tamaño ya admitido, el widget no se carga 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 rígida "Hola mundo".
Para ello, reemplace el texto codificado de forma rígida "Hola mundo" por widgetSettings.name
en la línea donde establecemos el texto del h2
elemento.
Esto garantiza que el nombre del widget se muestre cada vez que el widget se cargue en la actualización de 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 la reload
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 empaquetar y 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.
Comentarios
https://aka.ms/ContentUserFeedback.
Próximamente: A lo largo de 2024 iremos eliminando gradualmente las Cuestiones de GitHub como mecanismo de retroalimentación para el contenido y lo sustituiremos por un nuevo sistema de retroalimentación. Para más información, consulta:Enviar y ver comentarios de