Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
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. Comienza con un widget sencillo y termina 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.
Requisitos previos
Se requieren algunos conocimientos de JavaScript, HTML y CSS para el desarrollo de widgets.
Una organización en Azure DevOps. Cree una organización.
Un editor de texto. Para muchos de los tutoriales, usamos Visual Studio Code.
CLI multiplataforma para Azure DevOps (tfx-cli) para empaquetar las extensiones.
- tfx-cli se puede instalar mediante
npm
, un componente de Node.js ejecutandonpm i -g tfx-cli
- tfx-cli se puede instalar mediante
Un directorio principal para el proyecto. Este directorio se conoce como
home
en el tutorial y debe tener la siguiente estructura después de completar los pasos descritos en este artículo:|--- 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
En este tutorial
- Parte 1: Hola mundo: muestra cómo crear un nuevo widget, que imprime un sencillo mensaje Hola mundo .
- Parte 2: Hello World con la API REST de Azure DevOps: se basa en la primera parte agregando una llamada a una API rest de Azure DevOps.
- Parte 3: Configurar Hola mundo: explica cómo agregar la configuración al widget.
Nota:
Si tiene prisa y quiere poner las manos en el código inmediatamente, puede descargar la extensión de ejemplo para Azure DevOps.
Una vez descargado, vaya a la carpeta widgets
, luego siga directamente los pasos 6 y 7 para publicar la extensión de ejemplo que incluye tres widgets de ejemplo con distintas complejidades.
Comience con algunos estilos básicos para widgets que proporcionamos listos para usar y algo de orientación sobre la estructura de los widgets.
Parte 1: Hola mundo
La parte 1 presenta un widget que imprime Hello World mediante JavaScript.
Paso 1: Obtener el SDK de cliente
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 que la extensión está cargada u obtener contexto de la página actual.
Para recuperar el SDK, use el npm install
comando :
npm install vss-web-extension-sdk
Busque el archivo del SDK VSS.SDK.min.js
de cliente ubicado en la vss-web-extension-sdk/lib
carpeta y colóquelo en la home/sdk/scripts
carpeta.
Para obtener más información, consulte la página de GitHub del SDK de cliente.
Paso 2: Configurar la página HTML
Cree un archivo llamado hello-world.html
. La página HTML es el pegamento que mantiene la estructura unida e incluye referencias a CSS y JavaScript. Puede asignar un nombre a este archivo cualquier cosa. Si usa un nombre de archivo diferente, actualice todas las referencias a hello-world
con el nombre que use.
El widget se basa en HTML y se hospeda en un iframe.
Copie el código HTML siguiente en el hello-world.html
archivo. Agregamos la referencia obligatoria al VSS.SDK.min.js
archivo e incluimos un h2
elemento, que se actualiza con la cadena Hello World en el próximo paso.
<!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: Actualización de JavaScript
Usamos JavaScript para representar contenido en el widget. En este artículo, encapsulamos todo nuestro código JavaScript dentro de un elemento <script>
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 JavaScript también inicializa el SDK de VSS, asigna el código para tu widget al nombre del widget y notifica al framework de extensiones sobre los éxitos o errores del widget.
En este caso, el código siguiente imprime Hello World en el widget. Agregue este script
elemento en el head
del 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 intercambio de señales entre el iframe que aloja el widget y el marco del anfitrión.Introduzca
explicitNotifyLoaded: true
para que el widget pueda notificar explícitamente al host cuando termine de cargarse. Este control nos permite notificar la finalización de la carga después de asegurarse de que se cargan los módulos dependientes. UseusePlatformStyles: true
para que el widget pueda usar estilos principales de Azure DevOps para elementos HTML, comobody
ydiv
. Si no desea que el widget use estos estilos, paseusePlatformStyles: false
.VSS.require
se usa para cargar las bibliotecas de scripts 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. Después, pase el nombre del módulo correspondienteTFS/Dashboards/WidgetHelpers
y una devolución de llamada aVSS.require
. Se llama a la devolución de llamada una vez cargado el módulo. El callback contiene el resto del código JavaScript necesario para el widget. Al final de la devolución de llamada, llame aVSS.notifyLoadSucceeded
para notificar la finalización de la carga.WidgetHelpers.IncludeWidgetStyles
incluye una hoja de estilos con algunos CSS básicos para empezar. Para usar estos estilos, envuelve tu contenido dentro de un elemento HTML con la clasewidget
.VSS.register
se utiliza para mapear una función en JavaScript, lo cual identifica de manera única el widget entre las distintas contribuciones en tu extensión. El nombre debe coincidir con elid
que identifica su contribución como se describe en Paso 5. En el caso de los widgets, la función que se pasa aVSS.register
debe devolver un objeto que satisfaga elIWidget
contrato. Por ejemplo, el objeto devuelto debe tener una propiedad de carga cuyo valor sea otra función que tenga la lógica principal para representar el widget. En nuestro caso, es actualizar el texto delh2
elemento a Hello World. Es esta función la que se llama cuando el marco de widgets instancia tu widget.WidgetStatusHelper
Usamos desde WidgetHelpers para devolver comoWidgetStatus
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.
Paso 4: Actualizar el logotipo de la extensión
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 x 98 píxeles. Elija una imagen, asígnele el nombre logo.png
y colóquela en la carpeta img
. Puede asignar un nombre a la imagen como desee, siempre y cuando el manifiesto de extensión del siguiente paso se actualice con el nombre que utilice.
Paso 5: Crear el manifiesto de extensión
Cada extensión debe tener un archivo de manifiesto de extensión. Cree un archivo JSON llamado vss-extension.json
en el home
directorio con el siguiente contenido:
{
"manifestVersion": 1,
"id": "azure-devops-extensions-myExtensions",
"version": "1.0.0",
"name": "My First Set of Widgets",
"description": "Samples containing different widgets extending dashboards",
"publisher": "fabrikam",
"categories": ["Azure Boards"],
"targets": [
{
"id": "Microsoft.VisualStudio.Services"
}
],
"icons": {
"default": "img/logo.png"
},
"contributions": [
{
"id": "HelloWorldWidget",
"type": "ms.vss-dashboards-web.widget",
"targets": [
"ms.vss-dashboards-web.widget-catalog"
],
"properties": {
"name": "Hello World Widget",
"description": "My first widget",
"catalogIconUrl": "img/CatalogIcon.png",
"previewImageUrl": "img/preview.png",
"uri": "hello-world.html",
"supportedSizes": [
{
"rowSpan": 1,
"columnSpan": 2
}
],
"supportedScopes": ["project_team"]
}
}
],
"files": [
{
"path": "hello-world.html",
"addressable": true
},
{
"path": "sdk/scripts",
"addressable": true
},
{
"path": "img",
"addressable": true
}
]
}
El vss-extension.json
archivo siempre debe estar en la raíz del directorio principal. Para todos los demás archivos, puede colocarlos en la estructura que desee dentro de la carpeta, solo asegúrese de actualizar las referencias correctamente en los archivos HTML y en el vss-extension.json
manifiesto.
Para obtener más información sobre los atributos necesarios, consulte la referencia del manifiesto de extensión.
Nota:
Cambie publisher
por el nombre de su editor. Para crear un editor, consulte Package/Publish/Install.
Iconos
La parte icons
especifica la ruta del icono de la extensión en el manifiesto.
Contribuciones
Cada entrada de contribución define las propiedades.
El
id
para identificar su contribución. Este identificador debe ser único dentro de una extensión. Este identificador debe coincidir con el nombre que utilizó en el paso 3 para registrar su widget.El
type
de contribución. Para todos los widgets, el tipo debe serms.vss-dashboards-web.widget
.Matriz de
targets
a la que contribuye la contribución. Para todos los widgets, el destino debe ser[ms.vss-dashboards-web.widget-catalog]
.properties
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 tener 98 x 98 píxeles. Si usó 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 agregaste en Paso 4 para mostrar en el catálogo de widgets. La imagen debe ser de 330 x 160 píxeles. Si usó 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 usó 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 mayor que 1x1 obtiene un extra de 10 px que representa el margen entre widgets. Por ejemplo, un widget de 3x2 es160*3+10*2
de ancho y160*2+10*1
de alto. El tamaño máximo admitido es 4x4.supportedScopes
Actualmente, solo se admiten paneles de equipo. El valor debe ser project_team
. Las actualizaciones futuras pueden incluir más opciones para los ámbitos del panel.
Para más información sobre los puntos de contribución, consulte Puntos de extensibilidad.
Archivos
La files
estrofa indica los archivos que quiere 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 tfx-cli mediante npm
, un componente de Node.js, desde la línea de comandos.
npm i -g tfx-cli
Empaqueta tu extensión
El empaquetado de tu extensión en un archivo .vsix resulta fácil una vez que tienes 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:
Se debe incrementar una versión de extensión o integración en cada actualización.
Al actualizar una extensión existente, actualice la versión en el manifiesto o pase la opción de línea de comandos --rev-version
. Esto incrementa el número de versión de parche de tu extensión y guarda la nueva versión en el manifiesto.
Después de empaquetar la extensión en un archivo .vsix, está 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, cree uno.
Inicie sesión en el Portal de publicación de Visual Studio Marketplace.
Si aún no es miembro de un editor existente, debe crear un editor. Si ya tiene un editor, desplácese y seleccione Extensiones de publicación en Sitios relacionados.
- Especifique un identificador para el publicador, por ejemplo: mycompany-myteam.
- El identificador se usa como valor para el atributo
publisher
en el archivo de manifiesto de tus extensiones.
- El identificador se usa como valor para el atributo
- Especifique un nombre para mostrar para el publicador, por ejemplo: Mi equipo.
- Especifique un identificador para el publicador, por ejemplo: mycompany-myteam.
Revise el Acuerdo de Publicador de Marketplace y seleccione Crear.
Ahora tu publicador está definido. En una versión futura, puede conceder permisos para ver y administrar las extensiones del publicador.
La publicación de extensiones en un publicador común simplifica el proceso de equipos y organizaciones, ofreciendo un enfoque más seguro. Este método elimina la necesidad de distribuir un único conjunto de credenciales entre varios usuarios, lo que mejora la seguridad.
Actualice el archivo de manifiesto en los ejemplos para reemplazar el ID de publicador ficticio fabrikam con su ID de publicador.
Publicación y uso compartido de la extensión
Ahora, ahora puede cargar la extensión en Marketplace.
Seleccione 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 utilizando el comando tfx extension publish
en lugar de tfx extension create
para empaquetar y publicar la extensión en un solo paso. Opcionalmente, puede usar --share-with
para compartir su extensión con una o varias cuentas después de la publicación. También necesita un token de acceso personal.
tfx extension publish --manifest-globs your-manifest.json --share-with yourOrganization
Paso 7: Agregar widget desde el catálogo
Inicia sesión en tu proyecto,
http://dev.azure.com/{Your_Organization}/{Your_Project}
.Seleccione Overview>Dashboards.
Selecciona Agregar un widget.
Resalta tu widget y, a continuación, selecciona 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 el ejemplo siguiente, usamos la API REST para WorkItemTracking para capturar información sobre una consulta existente y mostrar información de consulta en el widget en el texto Hola mundo .
Paso 1: Agregar archivo 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 al ejemplo siguiente:
|--- README.md
|--- node_modules
|--- SDK
|--- scripts
|--- VSS.SDK.min.js
|--- img
|--- logo.png
|--- scripts
|--- hello-world.html // html page to be used for your widget
|--- hello-world2.html // renamed copy of hello-world.html
|--- vss-extension.json // extension's manifest
Para mantener la información de consulta, agregue un nuevo div
elemento bajo el h2
.
Actualiza el nombre del widget de HelloWorldWidget
a HelloWorldWidget2
en la línea donde llamas a VSS.register
.
Esta acción permite al marco identificar 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, á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. Para ver todos los ámbitos disponibles, consulte Ámbitos.
Agregue el código siguiente al final del manifiesto de extensión:
{
"scopes":[
"vso.work"
]
}
Para incluir otras propiedades, debe enumerarlas explícitamente, por ejemplo:
{
"name": "example-widget",
"publisher": "example-publisher",
"version": "1.0.0",
"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 cargó la extensión, quítelo de Marketplace. Vaya al Portal de publicación de Visual Studio Marketplace, seleccione la extensión con el botón derecho 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 conocen como clientes REST y son envoltorios de JavaScript en torno a las llamadas Ajax para todos los puntos de conexión disponibles del lado del servidor. Puede usar 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, actualice la llamada a VSS.require
para cargar AzureDevOps/WorkItemTracking/RestClient
, lo que proporciona el cliente REST de WorkItemTracking. Puede 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 a VSS.register
, cree una variable para contener el identificador del proyecto actual. Necesita esta variable para obtener la consulta. Además, cree un nuevo método getQueryInfo
para usar el cliente REST. A continuación, se llama a este método desde el método de carga.
El método getClient
proporciona una instancia del cliente REST que necesita. El método getQuery
devuelve la consulta ajustada en una promesa.
La actualización VSS.require
tiene el siguiente aspecto:
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers", "AzureDevOps/WorkItemTracking/RestClient"],
function (WidgetHelpers, TFS_Wit_WebApi) {
WidgetHelpers.IncludeWidgetStyles();
VSS.register("HelloWorldWidget2", function () {
var projectId = VSS.getWebContext().project.id;
var getQueryInfo = function (widgetSettings) {
// Get a WIT client to make REST calls to Azure DevOps Services
return TFS_Wit_WebApi.getClient().getQuery(projectId, "Shared Queries/Feedback")
.then(function (query) {
// Do something with the query
return WidgetHelpers.WidgetStatusHelper.Success();
}, function (error) {
return WidgetHelpers.WidgetStatusHelper.Failure(error.message);
});
}
return {
load: function (widgetSettings) {
// Set your title
var $title = $('h2.title');
$title.text('Hello World');
return getQueryInfo(widgetSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
Observe el uso del método Failure de WidgetStatusHelper
. Permite indicar al marco de widget que se produjo un error y aprovechar la experiencia de error estándar proporcionada a todos los widgets.
Si no tienes la Feedback
consulta en la carpeta Shared Queries
, sustituye Shared Queries\Feedback
en el código por la ruta de una consulta que exista en tu proyecto.
Paso 4: Mostrar la respuesta
El último paso es representar la información de consulta dentro del widget. La función getQuery
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 comentario // Do something with the query
con el siguiente código:
// 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);
Tu versión final hello-world2.html
es similar al siguiente ejemplo:
<!DOCTYPE html>
<html>
<head>
<script src="sdk/scripts/VSS.SDK.min.js"></script>
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers", "AzureDevOps/WorkItemTracking/RestClient"],
function (WidgetHelpers, TFS_Wit_WebApi) {
WidgetHelpers.IncludeWidgetStyles();
VSS.register("HelloWorldWidget2", function () {
var projectId = VSS.getWebContext().project.id;
var getQueryInfo = function (widgetSettings) {
// Get a WIT client to make REST calls to Azure DevOps Services
return TFS_Wit_WebApi.getClient().getQuery(projectId, "Shared Queries/Feedback")
.then(function (query) {
// Create a list with query details
var $list = $('<ul>');
$list.append($('<li>').text("Query ID: " + query.id));
$list.append($('<li>').text("Query Name: " + query.name));
$list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName : "<unknown>")));
// Append the list to the query-info-container
var $container = $('#query-info-container');
$container.empty();
$container.append($list);
// Use the widget helper and return success as Widget Status
return WidgetHelpers.WidgetStatusHelper.Success();
}, function (error) {
// Use the widget helper and return failure as Widget Status
return WidgetHelpers.WidgetStatusHelper.Failure(error.message);
});
}
return {
load: function (widgetSettings) {
// Set your title
var $title = $('h2.title');
$title.text('Hello World');
return getQueryInfo(widgetSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
</head>
<body>
<div class="widget">
<h2 class="title"></h2>
<div id="query-info-container"></div>
</div>
</body>
</html>
Paso 5: Actualizar el 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 propiedad contributions
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. Nómbrelo preview2.png
y colóquelo en la carpeta img
.
{
...,
"contributions": [
...,
{
"id": "HelloWorldWidget2",
"type": "ms.vss-dashboards-web.widget",
"targets": [
"ms.vss-dashboards-web.widget-catalog"
],
"properties": {
"name": "Hello World Widget 2 (with API)",
"description": "My second widget",
"previewImageUrl": "img/preview2.png",
"uri": "hello-world2.html",
"supportedSizes": [
{
"rowSpan": 1,
"columnSpan": 2
}
],
"supportedScopes": ["project_team"]
}
}
],
"files": [
{
"path": "hello-world.html",
"addressable": true
},
{
"path": "hello-world2.html",
"addressable": true
},
{
"path": "sdk/scripts",
"addressable": true
},
{
"path": "img",
"addressable": true
}
],
"scopes": [
"vso.work"
]
}
Paso 6: Empaquetar, publicar y compartir
Empaquete, publique y comparta su 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 Editar y seleccione Agregar. El catálogo de widgets se abre donde se encuentra el widget que instaló.
Para agregarlo al panel, elija el widget y seleccione Agregar.
Parte 3: Configurar Hola mundo
En Parte 2 de esta guía, viste cómo crear un widget que muestra información de consulta para una consulta predefinida. En esta parte, añadirá la capacidad de configurar la consulta que se usará en lugar de la codificada de forma fija. 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 de control cuando el usuario selecciona Guardar.
Paso 1: Agregar archivo HTML
Las implementaciones de widgets y configuraciones de widgets se parecen mucho. 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 siguiente HTML en configuration.html
. Básicamente, agrega la referencia obligatoria al archivo VSS.SDK.min.js
y un elemento select
para el desplegable a fin de 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: Configurar JavaScript
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, el código siguiente carga la configuración del widget.
Abra el archivo configuration.html
y añada el siguiente elemento <script>
a <head>
.
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers"], function (WidgetHelpers) {
VSS.register("HelloWorldWidget.Configuration", function () {
var $queryDropdown = $("#query-path-dropdown");
return {
load: function (widgetSettings, widgetConfigurationContext) {
var settings = JSON.parse(widgetSettings.customSettings.data);
if (settings && settings.queryPath) {
$queryDropdown.val(settings.queryPath);
}
return WidgetHelpers.WidgetStatusHelper.Success();
},
onSave: function() {
var customSettings = {
data: JSON.stringify({
queryPath: $queryDropdown.val()
})
};
return WidgetHelpers.WidgetConfigurationSave.Valid(customSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
VSS.init
,VSS.require
yVSS.register
desempeñan el mismo papel que desempeñaron para el widget descrito en la Parte 1. La única diferencia es que para las configuraciones de widget, la función que se pasa aVSS.register
debe devolver un objeto que satisfaga elIWidgetConfiguration
contrato.La
load
propiedad delIWidgetConfiguration
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 marcowidget configuration
La
onSave
propiedad delIWidgetConfiguration
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 elcustom settings
objeto y useWidgetConfigurationSave.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 a una cadena de texto.
Es accesible para el widget a través de la propiedad customSettings del objeto WidgetSettings
.
El widget tiene que deserializarse, lo cual se trata en el Paso 4.
Paso 3: Habilitación de la versión preliminar en vivo- JavaScript
Para habilitar la actualización en vista previa activa cuando el usuario selecciona una consulta en la lista desplegable, adjunte un controlador de eventos de cambio al botón. Este controlador notifica al marco que cambió 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 el siguiente código en la función asignada a la propiedad load
.
$queryDropdown.on("change", function () {
var customSettings = {
data: JSON.stringify({
queryPath: $queryDropdown.val()
})
};
var eventName = WidgetHelpers.WidgetEvent.ConfigurationChange;
var eventArgs = WidgetHelpers.WidgetEvent.Args(customSettings);
widgetConfigurationContext.notify(eventName, eventArgs);
});
Asegúrese de que el marco recibe una notificación del cambio de configuración al menos una vez para habilitar el botón Guardar .
Al final, tu configuration.html
tiene un aspecto similar al ejemplo siguiente:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="sdk/scripts/VSS.SDK.min.js"></script>
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers"], function (WidgetHelpers) {
VSS.register("HelloWorldWidget.Configuration", function () {
var $queryDropdown = $("#query-path-dropdown");
return {
load: function (widgetSettings, widgetConfigurationContext) {
var settings = JSON.parse(widgetSettings.customSettings.data);
if (settings && settings.queryPath) {
$queryDropdown.val(settings.queryPath);
}
$queryDropdown.on("change", function () {
var customSettings = {data: JSON.stringify({queryPath: $queryDropdown.val()})};
var eventName = WidgetHelpers.WidgetEvent.ConfigurationChange;
var eventArgs = WidgetHelpers.WidgetEvent.Args(customSettings);
widgetConfigurationContext.notify(eventName, eventArgs);
});
return WidgetHelpers.WidgetStatusHelper.Success();
},
onSave: function() {
var customSettings = {data: JSON.stringify({queryPath: $queryDropdown.val()})};
return WidgetHelpers.WidgetConfigurationSave.Valid(customSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
</head>
<body>
<div class="container">
<fieldset>
<label class="label">Query: </label>
<select id="query-path-dropdown" style="margin-top:10px">
<option value="" selected disabled hidden>Please select a query</option>
<option value="Shared Queries/Feedback">Shared Queries/Feedback</option>
<option value="Shared Queries/My Bugs">Shared Queries/My Bugs</option>
<option value="Shared Queries/My Tasks">Shared Queries/My Tasks</option>
</select>
</fieldset>
</div>
</body>
</html>
Paso 4: Implementación de la recarga en el widget: JavaScript
Configura el widget para almacenar la ruta de consulta seleccionada por el usuario.
Ahora tiene 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
.
Esta acción permite al marco identificar 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 nuestro widget ahora necesita configuración, esta función debe actualizarse para devolver un objeto que satisfaga el contrato IConfigurableWidget
.
Para ello, actualice la instrucción return para incluir una propiedad denominada reload por el código siguiente. 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 a este método de recarga 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 consulta codificada de forma rígida en getQueryInfo
debe reemplazarse por la ruta de consulta configurada, que se puede extraer del parámetro widgetSettings
que se pasa al método.
Agregue el siguiente código al comienzo del método getQueryInfo
y reemplace la ruta de consulta codificada rígidamente con 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 propiedades load
como reload
tienen una función similar. Este es el caso de la mayoría de los widgets simples.
En el caso de widgets complejos, hay 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 forman parte de la función correspondiente a la load
propiedad y no a la reload
propiedad .
Paso 5: Actualizar el manifiesto de extensión
Abra el vss-extension.json
archivo para incluir dos entradas nuevas en la matriz en la contributions
propiedad : una para el HelloWorldWidget3
widget y la otra para su configuración.
Necesita otra imagen de vista previa para el tercer widget. Nombra esto preview3.png
y colócalo en la carpeta img
.
Actualice la matriz de la files
propiedad para incluir los dos nuevos archivos HTML que agregó en este ejemplo.
{
...
"contributions": [
... ,
{
"id": "HelloWorldWidget3",
"type": "ms.vss-dashboards-web.widget",
"targets": [
"ms.vss-dashboards-web.widget-catalog",
"fabrikam.azuredevops-extensions-myExtensions.HelloWorldWidget.Configuration"
],
"properties": {
"name": "Hello World Widget 3 (with config)",
"description": "My third widget",
"previewImageUrl": "img/preview3.png",
"uri": "hello-world3.html",
"supportedSizes": [
{
"rowSpan": 1,
"columnSpan": 2
}
],
"supportedScopes": ["project_team"]
}
},
{
"id": "HelloWorldWidget.Configuration",
"type": "ms.vss-dashboards-web.widget-configuration",
"targets": [ "ms.vss-dashboards-web.widget-configuration" ],
"properties": {
"name": "HelloWorldWidget Configuration",
"description": "Configures HelloWorldWidget",
"uri": "configuration.html"
}
}
],
"files": [
{
"path": "hello-world.html", "addressable": true
},
{
"path": "hello-world2.html", "addressable": true
},
{
"path": "hello-world3.html", "addressable": true
},
{
"path": "configuration.html", "addressable": true
},
{
"path": "sdk/scripts", "addressable": true
},
{
"path": "img", "addressable": true
}
],
...
}
La 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:
- El
id
para identificar su contribución. El identificador debe ser único dentro de una extensión. - El
type
de contribución. Para todas las configuraciones de widget, debe serms.vss-dashboards-web.widget-configuration
. - Matriz de
targets
a la que contribuye la contribución. Para todas las configuraciones de widget, tiene una sola entrada:ms.vss-dashboards-web.widget-configuration
. properties
que contiene un conjunto de propiedades que incluye 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 targets
para el widget debe actualizarse para incluir el identificador de la configuración de la siguiente forma:
<publisher>.<id for the extension>.<id for the configuration contribution>
En este caso, el ejemplo 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. Para obtener el archivo de manifiesto de ejemplo completo, consulte vss-extension.json.
Paso 6: Empaquetar, publicar y compartir
Si la extensión no está publicada, consulte Paso 6: Empaquetar, publicar y compartir. 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 Editar y seleccione Agregar. Esta acción debe abrir el catálogo de widgets donde encuentre el widget que instaló.
Para agregar el widget al panel, elija el widget y seleccione Agregar.
Un mensaje similar al siguiente le pide que configure el widget.
Hay dos maneras de configurar widgets. Una forma 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 tu widget muestra los resultados actualizados.
Paso 8: Configurar más (opcional)
Puede agregar tantos elementos de formulario HTML como necesite en configuration.html
para más configuraciones.
Hay dos características configurables disponibles de fábrica: 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 agregue a un panel.
Puede permitir que los usuarios configuren, de modo 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 de su 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 tendría el siguiente aspecto si habilita 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.azuredevops-extensions-myExtensions.HelloWorldWidget.Configuration"
],
"properties": {
"name": "Hello World Widget 3 (with config)",
"description": "My third widget",
"previewImageUrl": "img/preview3.png",
"uri": "hello-world3.html",
"isNameConfigurable": true,
"supportedSizes": [
{
"rowSpan": 1,
"columnSpan": 2
},
{
"rowSpan": 2,
"columnSpan": 2
}
],
"supportedScopes": ["project_team"]
}
},
...
]
}
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.
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 del panel también cambiará de tamaño.
Cambiar el nombre del widget no da lugar a ningún cambio visible en el widget porque nuestros widgets de ejemplo no muestran el nombre del widget en ningún lugar.
Modifique el código de ejemplo para mostrar el nombre del widget en lugar del texto codificado de forma rígida Hello World reemplazando el texto codificado de forma rígida Hello World por widgetSettings.name
en la línea donde se establece el texto del h2
elemento.
Esta acción garantiza que el nombre del widget se muestre cada vez que el widget se cargue en la actualización de página.
Puesto que quiere que la vista previa en vivo se actualice cada vez que cambie la configuración, también debe agregar el mismo código en la parte reload
de su 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.