Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Azure DevOps Services | Azure DevOps Server | Azure DevOps Server 2022
Os widgets são implementados como contribuições na estrutura de extensão. Uma única extensão pode incluir várias contribuições de widget. Este artigo mostra como criar uma extensão que fornece um ou mais widgets.
Dica
Confira a nossa documentação mais nova sobre desenvolvimento de extensões usando o Azure DevOps Extension SDK.
Dica
Se você estiver iniciando uma nova extensão do Azure DevOps, experimente essas coleções de exemplo mantidas primeiro— elas funcionam com builds de produtos atuais e abrangem cenários modernos (por exemplo, adicionando guias em páginas de solicitação pull).
- Exemplo de extensão do Azure DevOps (GitHub)— um exemplo de inicialização compacta que demonstra padrões de extensão comuns: https://github.com/microsoft/azure-devops-extension-sample
- Exemplos de extensão do Azure DevOps (coleção legada e guia de contribuições) – instale para inspecionar os alvos da UI ou exiba a origem: https://marketplace.visualstudio.com/items/ms-samples.samples-contributions-guide e https://github.com/Microsoft/vso-extension-samples/tree/master/contributions-guide
- Exemplos do Microsoft Learn (navegar por amostras do Azure DevOps) — amostras atualizadas e selecionadas em documentos da Microsoft: /samples/browse/?terms=azure%20devops%20extension
Se um exemplo não funcionar em sua organização, instale-o em uma organização pessoal ou de teste e compare as IDs de destino do manifesto de extensão e as versões da API com as documentações atuais. Para referência e APIs, consulte:
Pré-requisitos
| Requisito | Descrição |
|---|---|
| Conhecimento de programação | Conhecimento de JavaScript, HTML e CSS para desenvolvimento de widget |
| Organização do Azure DevOps | Criar uma organização |
| Editor de texto | Usamos o Visual Studio Code para tutoriais |
| Node.js | Versão mais recente do Node.js |
| CLI multiplataforma |
tfx-cli para empacotar extensões Instale usando: npm i -g tfx-cli |
| Diretório do projeto | Diretório inicial com essa estrutura depois de concluir o tutorial:|--- README.md|--- sdk |--- node_modules |--- scripts |--- VSS.SDK.min.js|--- img |--- logo.png|--- scripts|--- hello-world.html // html page for your widget|--- vss-extension.json // extension manifest |
Visão geral dos tutoriais
Este tutorial ensina o desenvolvimento de widget por meio de três exemplos progressivos:
| Parte | Foco | O que você aprenderá |
|---|---|---|
| Parte 1: Olá, Mundo | Criação básica de widget | Criar um widget que exibe texto |
| Parte 2: integração da API REST | Chamadas à API do Azure DevOps | Adicionar funcionalidade da API REST para buscar e exibir dados |
| Parte 3: Configuração do Widget | Personalização do usuário | Implementar opções de configuração para o widget |
Dica
Se você preferir ir direto para exemplos de trabalho, os exemplos incluídos (consulte a nota anterior) mostram um conjunto de widgets que você pode empacotar e publicar.
Antes de começar, examine os estilos básicos de widget e as diretrizes estruturais que fornecemos.
Parte 1: Olá, Mundo
Crie um widget básico que exibe "Olá, Mundo" usando JavaScript. Esta fundação demonstra os principais conceitos de desenvolvimento de widget.
Etapa 1: Instalar o SDK do cliente
O SDK do VSS permite que seu widget se comunique com o Azure DevOps. Instale-o usando o npm:
npm install vss-web-extension-sdk
Copie o arquivo VSS.SDK.min.js de vss-web-extension-sdk/lib para a sua pasta home/sdk/scripts.
Para obter mais documentação do SDK, consulte a página do GitHub do SDK do Cliente.
Etapa 2: Criar a estrutura HTML
Crie hello-world.html no diretório do projeto. Esse arquivo fornece o layout do widget e as referências aos scripts necessários.
<!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>
Os widgets são executados em iframes, portanto, a maioria dos elementos de cabeçalho HTML exceto <script> e <link> são ignorados pela estrutura.
Etapa 3: Adicionar o widget JavaScript
Para implementar a funcionalidade do widget, adicione este script à <head> seção do arquivo HTML:
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers"], function (WidgetHelpers) {
WidgetHelpers.IncludeWidgetStyles();
VSS.register("HelloWorldWidget", function () {
return {
load: function (widgetSettings) {
var $title = $('h2.title');
$title.text('Hello World');
return WidgetHelpers.WidgetStatusHelper.Success();
}
};
});
VSS.notifyLoadSucceeded();
});
</script>
Principais componentes javaScript
| Função | Propósito |
|---|---|
VSS.init() |
Inicializa a comunicação entre o widget e o Azure DevOps |
VSS.require() |
Carrega bibliotecas de SDK necessárias e auxiliares de widget |
VSS.register() |
Registra seu widget com um identificador exclusivo |
WidgetHelpers.IncludeWidgetStyles() |
Aplica o estilo padrão do Azure DevOps |
VSS.notifyLoadSucceeded() |
Notifica a estrutura que o carregamento foi concluído com êxito |
Importante
O nome do widget em VSS.register() deve corresponder ao id no manifesto da sua extensão (Etapa 5).
Etapa 4: Adicionar imagens de extensão
Crie as imagens necessárias para sua extensão:
-
Logotipo da extensão: imagem de 98 x 98 pixels nomeada
logo.pngnaimgpasta -
Ícone do catálogo do Widget: imagem de 98 x 98 pixels nomeada
CatalogIcon.pngnaimgpasta -
Visualização do widget: imagem de 330 x 160 pixels nomeada
preview.pngnaimgpasta
Essas imagens são exibidas no catálogo do Marketplace e do widget quando os usuários navegam por extensões disponíveis.
Etapa 5: Criar o manifesto da extensão
Crie vss-extension.json no diretório raiz do projeto. Este arquivo define os metadados e as contribuições da extensão:
{
"manifestVersion": 1,
"id": "azure-devops-extensions-myExtensions",
"version": "1.0.0",
"name": "My First Set of Widgets",
"description": "Samples containing different widgets extending dashboards",
"publisher": "fabrikam",
"categories": ["Azure Boards"],
"targets": [
{
"id": "Microsoft.VisualStudio.Services"
}
],
"icons": {
"default": "img/logo.png"
},
"contributions": [
{
"id": "HelloWorldWidget",
"type": "ms.vss-dashboards-web.widget",
"targets": [
"ms.vss-dashboards-web.widget-catalog"
],
"properties": {
"name": "Hello World Widget",
"description": "My first widget",
"catalogIconUrl": "img/CatalogIcon.png",
"previewImageUrl": "img/preview.png",
"uri": "hello-world.html",
"supportedSizes": [
{
"rowSpan": 1,
"columnSpan": 2
}
],
"supportedScopes": ["project_team"]
}
}
],
"files": [
{
"path": "hello-world.html",
"addressable": true
},
{
"path": "sdk/scripts",
"addressable": true
},
{
"path": "img",
"addressable": true
}
]
}
Importante
Substitua "publisher": "fabrikam" pelo nome real do editor. Saiba como criar um publicador.
Propriedades essenciais do manifesto
| Seção | Propósito |
|---|---|
| Informações básicas | Nome da extensão, versão, descrição e editor |
| Ícones | Caminhos para os ativos visuais da sua extensão |
| Contribuições | Definições de widget, incluindo ID, tipo e propriedades |
| Arquivos | Todos os arquivos a serem incluídos no pacote de extensão |
Para ver a documentação completa do manifesto, consulte Referência do manifesto da extensão.
Etapa 6: Empacotar e publicar sua extensão
Empacote sua extensão e publique-a no Visual Studio Marketplace.
Instalar a ferramenta de empacotamento
npm i -g tfx-cli
Criar seu pacote de extensão
No diretório do projeto, execute:
tfx extension create --manifest-globs vss-extension.json
Essa ação cria um .vsix arquivo que contém sua extensão empacotada.
Configurar um publicador
- Acesse o Portal de Publicação do Visual Studio Marketplace.
- Entre e crie um publicador se você não tiver um.
- Escolha um identificador exclusivo do editor (usado no arquivo de manifesto).
- Atualize
vss-extension.jsonpara usar o nome do seu editor em vez de "fabrikam".
Carregar sua extensão
- No Portal de Publicação, selecione Carregar nova extensão.
- Escolha seu
.vsixarquivo e carregue-o. - Compartilhe a extensão com sua organização do Azure DevOps.
Como alternativa, use a linha de comando:
tfx extension publish --manifest-globs vss-extension.json --share-with yourOrganization
Dica
Use --rev-version para incrementar automaticamente o número de versão ao atualizar uma extensão existente.
Etapa 7: Instalar e testar seu widget
Para testar, adicione seu widget a um painel:
- Acesse seu projeto do Azure DevOps:
https://dev.azure.com/{Your_Organization}/{Your_Project}. - Vá para Visão Geral>Painéis.
- Selecione Adicionar um widget.
- Localize o widget no catálogo e selecione Adicionar.
O widget "Olá, Mundo" aparece no painel, exibindo o texto configurado.
Próxima etapa: Continue para a Parte 2 para saber como integrar as APIs REST do Azure DevOps ao seu widget.
Parte 2: Olá, Mundo com a API REST do Azure DevOps
Estenda o widget para interagir com os dados do Azure DevOps usando APIs REST. Este exemplo demonstra como buscar informações de consulta e exibi-la dinamicamente em seu widget.
Nesta parte, use a API REST de Acompanhamento de Item de Trabalho para recuperar informações sobre uma consulta existente e exibir os detalhes da consulta abaixo do texto "Olá, Mundo".
Etapa 1: Criar o arquivo HTML aprimorado
Crie um novo arquivo de widget que se baseia no exemplo anterior. Copie hello-world.html e renomeie-o para hello-world2.html. A estrutura do projeto agora inclui:
|--- README.md
|--- node_modules
|--- sdk/
|--- scripts/
|--- VSS.SDK.min.js
|--- img/
|--- logo.png
|--- scripts/
|--- hello-world.html // Part 1 widget
|--- hello-world2.html // Part 2 widget (new)
|--- vss-extension.json // Extension manifest
Atualizar a estrutura HTML do widget
Faça estas alterações em hello-world2.html:
-
Adicionar um contêiner para dados de consulta: inclua um novo
<div>elemento para exibir informações de consulta. -
Atualize o identificador do widget: mude o nome do widget de
HelloWorldWidgetparaHelloWorldWidget2para identificação única.
<!DOCTYPE html>
<html>
<head>
<script src="sdk/scripts/VSS.SDK.min.js"></script>
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers"], function (WidgetHelpers) {
WidgetHelpers.IncludeWidgetStyles();
VSS.register("HelloWorldWidget2", function () {
return {
load: function (widgetSettings) {
var $title = $('h2.title');
$title.text('Hello World');
return WidgetHelpers.WidgetStatusHelper.Success();
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
</head>
<body>
<div class="widget">
<h2 class="title"></h2>
<div id="query-info-container"></div>
</div>
</body>
</html>
Etapa 2: Configurar permissões de acesso à API
Antes de fazer chamadas à API REST, configure as permissões necessárias no manifesto da extensão.
Adicionar o escopo de trabalho
O escopo vso.work concede acesso somente leitura a itens de trabalho e consultas. Adicione este escopo a vss-extension.json:
{
"scopes": [
"vso.work"
]
}
Exemplo de manifesto completo
Para um manifesto completo com outras propriedades, estruture-o assim:
{
"name": "example-widget",
"publisher": "example-publisher",
"version": "1.0.0",
"scopes": [
"vso.work"
]
}
Importante
Limitações de escopo: não há suporte para adicionar ou alterar escopos após a publicação. Se você já tiver publicado sua extensão, deverá removê-la do Marketplace primeiro. Acesse o Portal de Publicação do Visual Studio Marketplace, localize sua extensão e selecione Remover.
Etapa 3: Implementar a integração da API REST
O Azure DevOps fornece bibliotecas de cliente REST do JavaScript por meio do SDK. Essas bibliotecas encapsulam chamadas AJAX e mapeiam respostas de API para objetos utilizáveis.
Atualizar o widget JavaScript
Substitua a chamada VSS.require em hello-world2.html para incluir o cliente REST de Rastreamento de Itens de Trabalho:
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers", "AzureDevOps/WorkItemTracking/RestClient"],
function (WidgetHelpers, WorkItemTrackingRestClient) {
WidgetHelpers.IncludeWidgetStyles();
VSS.register("HelloWorldWidget2", function () {
var projectId = VSS.getWebContext().project.id;
var getQueryInfo = function (widgetSettings) {
// Get a WIT client to make REST calls to Azure DevOps Services
return WorkItemTrackingRestClient.getClient().getQuery(projectId, "Shared Queries/Feedback")
.then(function (query) {
// Process query data (implemented in Step 4)
return WidgetHelpers.WidgetStatusHelper.Success();
}, function (error) {
return WidgetHelpers.WidgetStatusHelper.Failure(error.message);
});
}
return {
load: function (widgetSettings) {
// Set your title
var $title = $('h2.title');
$title.text('Hello World');
return getQueryInfo(widgetSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
Principais detalhes de implementação
| Componente | Propósito |
|---|---|
WorkItemTrackingRestClient.getClient() |
Obtém uma instância do cliente REST de Rastreamento de Itens de Trabalho |
getQuery() |
Recupera informações de consulta encapsuladas em uma promessa |
WidgetStatusHelper.Failure() |
Fornece tratamento de erros consistente para falhas de widget |
projectId |
Contexto de projeto atual necessário para chamadas à API |
Dica
Caminhos de consulta personalizados: se você não tiver uma consulta "Comentários" em "Consultas Compartilhadas", substitua "Shared Queries/Feedback" pelo caminho para qualquer consulta que exista em seu projeto.
Etapa 4: Exibir dados de resposta da API
Renderize as informações de consulta em seu widget processando a resposta da API REST.
Adicionar renderização de dados de consulta
Substitua o // Process query data comentário por esta implementação:
// 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);
O getQuery() método retorna um Contracts.QueryHierarchyItem objeto com propriedades para metadados de consulta. Este exemplo exibe três partes principais de informações abaixo do texto "Olá, Mundo".
Exemplo de trabalho completo
Seu arquivo final hello-world2.html deve ter esta aparência:
<!DOCTYPE html>
<html>
<head>
<script src="sdk/scripts/VSS.SDK.min.js"></script>
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers", "AzureDevOps/WorkItemTracking/RestClient"],
function (WidgetHelpers, WorkItemTrackingRestClient) {
WidgetHelpers.IncludeWidgetStyles();
VSS.register("HelloWorldWidget2", function () {
var projectId = VSS.getWebContext().project.id;
var getQueryInfo = function (widgetSettings) {
// Get a WIT client to make REST calls to Azure DevOps Services
return WorkItemTrackingRestClient.getClient().getQuery(projectId, "Shared Queries/Feedback")
.then(function (query) {
// Create a list with query details
var $list = $('<ul>');
$list.append($('<li>').text("Query ID: " + query.id));
$list.append($('<li>').text("Query Name: " + query.name));
$list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName : "<unknown>")));
// Append the list to the query-info-container
var $container = $('#query-info-container');
$container.empty();
$container.append($list);
// Use the widget helper and return success as Widget Status
return WidgetHelpers.WidgetStatusHelper.Success();
}, function (error) {
// Use the widget helper and return failure as Widget Status
return WidgetHelpers.WidgetStatusHelper.Failure(error.message);
});
}
return {
load: function (widgetSettings) {
// Set your title
var $title = $('h2.title');
$title.text('Hello World');
return getQueryInfo(widgetSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
</head>
<body>
<div class="widget">
<h2 class="title"></h2>
<div id="query-info-container"></div>
</div>
</body>
</html>
Etapa 5: Atualizar o manifesto da extensão
Para disponibilizá-lo no catálogo do widget, adicione seu novo widget ao manifesto da extensão.
Adicionar a segunda contribuição de widget
Atualize vss-extension.json para incluir o widget habilitado para API REST. Adicione essa contribuição à contributions matriz:
{
"contributions": [
// ...existing HelloWorldWidget contribution...,
{
"id": "HelloWorldWidget2",
"type": "ms.vss-dashboards-web.widget",
"targets": [
"ms.vss-dashboards-web.widget-catalog"
],
"properties": {
"name": "Hello World Widget 2 (with API)",
"description": "My second widget",
"previewImageUrl": "img/preview2.png",
"uri": "hello-world2.html",
"supportedSizes": [
{
"rowSpan": 1,
"columnSpan": 2
}
],
"supportedScopes": ["project_team"]
}
}
],
"files": [
{
"path": "hello-world.html",
"addressable": true
},
{
"path": "hello-world2.html",
"addressable": true
},
{
"path": "sdk/scripts",
"addressable": true
},
{
"path": "img",
"addressable": true
}
],
"scopes": [
"vso.work"
]
}
Dica
Imagem de visualização: crie uma preview2.png imagem (330 x 160 pixels) e coloque-a img na pasta para mostrar aos usuários a aparência do widget no catálogo.
Etapa 6: Empacotar, publicar e compartilhar
Empacotar, publicar e compartilhar sua extensão. Se você já tiver publicado a extensão, poderá reempacotá-la e atualizá-la diretamente no Marketplace.
Etapa 7: Testar o widget da API REST
Para exibir a integração da API REST em ação, adicione o novo widget ao painel:
- Acesse seu projeto do Azure DevOps:
https://dev.azure.com/{Your_Organization}/{Your_Project}. - Selecione Visão Geral>Painéis.
- Selecione Adicionar um widget.
- Encontre "Hello World Widget 2 (com API)" e selecione Adicionar.
O widget aprimorado exibe o texto "Olá, Mundo" e as informações de consulta ao vivo do seu projeto do Azure DevOps.
Próximas etapas: Continue para a Parte 3 para adicionar opções de configuração que permitem aos usuários personalizar qual consulta exibir.
Parte 3: Configurar Hello World
Crie com base na Parte 2 adicionando funcionalidades de configuração do usuário ao widget. Em vez de codificar o caminho da consulta, crie uma interface de configuração que permita que os usuários selecionem qual consulta exibir, com funcionalidade de visualização ao vivo.
Essa parte demonstra como criar widgets configuráveis que os usuários podem personalizar para suas necessidades específicas, fornecendo comentários em tempo real durante a configuração.
Etapa 1: Criar arquivos de configuração
As configurações de widget compartilham muitas semelhanças com os próprios widgets , ambos usam os mesmos padrões SDK, HTML e JavaScript, mas servem a diferentes finalidades dentro da estrutura de extensão.
Configurar a estrutura do projeto
Para dar suporte à configuração de widget, crie dois novos arquivos:
- Copie
hello-world2.htmle renomeie-o parahello-world3.htmlo widget configurável. - Crie um novo arquivo chamado
configuration.html, que manipula a interface de configuração.
A estrutura do projeto agora inclui:
|--- README.md
|--- sdk/
|--- node_modules
|--- scripts/
|--- VSS.SDK.min.js
|--- img/
|--- logo.png
|--- scripts/
|--- configuration.html // New: Configuration interface
|--- hello-world.html // Part 1: Basic widget
|--- hello-world2.html // Part 2: REST API widget
|--- hello-world3.html // Part 3: Configurable widget (new)
|--- vss-extension.json // Extension manifest
Criar a interface de configuração
Adicione essa estrutura HTML ao configuration.html, que cria um menu suspenso para a escolha de consultas.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="sdk/scripts/VSS.SDK.min.js"></script>
</head>
<body>
<div class="container">
<fieldset>
<label class="label">Query: </label>
<select id="query-path-dropdown" style="margin-top:10px">
<option value="" selected disabled hidden>Please select a query</option>
<option value="Shared Queries/Feedback">Shared Queries/Feedback</option>
<option value="Shared Queries/My Bugs">Shared Queries/My Bugs</option>
<option value="Shared Queries/My Tasks">Shared Queries/My Tasks</option>
</select>
</fieldset>
</div>
</body>
</html>
Etapa 2: Implementar o JavaScript de configuração
O JavaScript de configuração segue o mesmo padrão de inicialização que os widgets, mas implementa o IWidgetConfiguration contrato em vez do contrato básico IWidget .
Adicionar lógica de configuração
Insira este script na <head> seção de configuration.html:
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers"], function (WidgetHelpers) {
VSS.register("HelloWorldWidget.Configuration", function () {
var $queryDropdown = $("#query-path-dropdown");
return {
load: function (widgetSettings, widgetConfigurationContext) {
var settings = JSON.parse(widgetSettings.customSettings.data);
if (settings && settings.queryPath) {
$queryDropdown.val(settings.queryPath);
}
return WidgetHelpers.WidgetStatusHelper.Success();
},
onSave: function() {
var customSettings = {
data: JSON.stringify({
queryPath: $queryDropdown.val()
})
};
return WidgetHelpers.WidgetConfigurationSave.Valid(customSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
Detalhes do contrato de configuração
O IWidgetConfiguration contrato requer estas funções principais:
| Função | Propósito | Quando chamado |
|---|---|---|
load() |
Inicializar a interface do usuário de configuração com as configurações existentes | Quando a caixa de diálogo de configuração é aberta |
onSave() |
Serializar a entrada do usuário e validar as configurações | Quando o usuário seleciona Salvar |
Dica
Serialização de dados: este exemplo usa JSON para serializar as configurações. O widget acessa essas configurações via widgetSettings.customSettings.data e deve desserializá-las adequadamente.
Etapa 3: Habilitar a funcionalidade de visualização ao vivo
A visualização ao vivo permite que os usuários vejam as alterações do widget imediatamente à medida que modificam as configurações, fornecendo comentários instantâneos antes de salvar.
Implementar notificações de alteração
Para habilitar a visualização ao vivo, adicione este manipulador de eventos dentro da load função:
$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);
});
Concluir o arquivo de configuração
Sua final configuration.html deve ter esta aparência:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="sdk/scripts/VSS.SDK.min.js"></script>
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers"], function (WidgetHelpers) {
VSS.register("HelloWorldWidget.Configuration", function () {
var $queryDropdown = $("#query-path-dropdown");
return {
load: function (widgetSettings, widgetConfigurationContext) {
var settings = JSON.parse(widgetSettings.customSettings.data);
if (settings && settings.queryPath) {
$queryDropdown.val(settings.queryPath);
}
$queryDropdown.on("change", function () {
var customSettings = {data: JSON.stringify({queryPath: $queryDropdown.val()})};
var eventName = WidgetHelpers.WidgetEvent.ConfigurationChange;
var eventArgs = WidgetHelpers.WidgetEvent.Args(customSettings);
widgetConfigurationContext.notify(eventName, eventArgs);
});
return WidgetHelpers.WidgetStatusHelper.Success();
},
onSave: function() {
var customSettings = {data: JSON.stringify({queryPath: $queryDropdown.val()})};
return WidgetHelpers.WidgetConfigurationSave.Valid(customSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
</head>
<body>
<div class="container">
<fieldset>
<label class="label">Query: </label>
<select id="query-path-dropdown" style="margin-top:10px">
<option value="" selected disabled hidden>Please select a query</option>
<option value="Shared Queries/Feedback">Shared Queries/Feedback</option>
<option value="Shared Queries/My Bugs">Shared Queries/My Bugs</option>
<option value="Shared Queries/My Tasks">Shared Queries/My Tasks</option>
</select>
</fieldset>
</div>
</body>
</html>
Importante
Botão Habilitar Salvar: a estrutura requer pelo menos uma notificação de alteração de configuração para habilitar o botão Salvar . O manipulador de eventos de alteração garante que essa ação ocorra quando os usuários selecionam uma opção.
Etapa 4: tornar o widget configurável
Transforme o widget da Parte 2 para usar dados de configuração em vez de valores embutidos em código. Esta etapa requer a implementação do IConfigurableWidget contrato.
Atualizar o registro do widget
In hello-world3.html, faça estas alterações:
-
Atualizar iD do widget: alterar de
HelloWorldWidget2paraHelloWorldWidget3. -
Adicionar função de recarregamento: implemente o contrato
IConfigurableWidget.
return {
load: function (widgetSettings) {
// Set your title
var $title = $('h2.title');
$title.text('Hello World');
return getQueryInfo(widgetSettings);
},
reload: function (widgetSettings) {
return getQueryInfo(widgetSettings);
}
}
Manipular dados de configuração
Atualize a função getQueryInfo para usar as configurações em vez de caminhos de consulta pré-definidos.
var settings = JSON.parse(widgetSettings.customSettings.data);
if (!settings || !settings.queryPath) {
var $container = $('#query-info-container');
$container.empty();
$container.text("Please configure a query path to display data.");
return WidgetHelpers.WidgetStatusHelper.Success();
}
Diferenças no ciclo de vida do widget
| Função | Propósito | Diretrizes de uso |
|---|---|---|
load() |
Renderização inicial do widget e configuração única | Operações pesadas, inicialização de recursos |
reload() |
Atualizar o widget com a nova configuração | Atualizações leves, atualização de dados |
Dica
Otimização de desempenho: use load() para operações caras que só precisam ser executadas uma vez e reload() para atualizações rápidas quando a configuração for alterada.
(Opcional) Adicionar uma caixa de luz para obter informações detalhadas
Os widgets de painel têm espaço limitado, tornando desafiador exibir informações abrangentes. Uma lightbox oferece uma solução elegante ao exibir dados detalhados em uma sobreposição modal, sem a necessidade de navegar para fora do painel.
Por que usar uma caixa de luz em widgets?
| Benefício | Descrição |
|---|---|
| Eficiência de espaço | Manter o widget compacto ao oferecer exibições detalhadas |
| Experiência do usuário | Manter o contexto do painel enquanto mostra mais informações |
| Divulgação progressiva | Mostrar dados de resumo no widget, detalhes sob demanda |
| Design responsivo | Adaptar-se a diferentes tamanhos de tela e configurações de widget |
Implementar elementos clicáveis
Atualize a renderização de dados de consulta para incluir elementos clicáveis que disparam a caixa de luz:
// Create a list with clickable query details
var $list = $('<ul class="query-summary">');
$list.append($('<li>').text("Query ID: " + query.id));
$list.append($('<li>').text("Query Name: " + query.name));
$list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName : "<unknown>"));
// Add a clickable element to open detailed view
var $detailsLink = $('<button class="details-link">View Details</button>');
$detailsLink.on('click', function() {
showQueryDetails(query);
});
// Append to the container
var $container = $('#query-info-container');
$container.empty();
$container.append($list);
$container.append($detailsLink);
Criar a funcionalidade do lightbox
Adicione esta implementação de lightbox ao seu widget JavaScript:
function showQueryDetails(query) {
// Create lightbox overlay
var $overlay = $('<div class="lightbox-overlay">');
var $lightbox = $('<div class="lightbox-content">');
// Add close button
var $closeBtn = $('<button class="lightbox-close">×</button>');
$closeBtn.on('click', function() {
$overlay.remove();
});
// Create detailed content
var $content = $('<div class="query-details">');
$content.append($('<h3>').text(query.name || 'Query Details'));
$content.append($('<p>').html('<strong>ID:</strong> ' + query.id));
$content.append($('<p>').html('<strong>Path:</strong> ' + query.path));
$content.append($('<p>').html('<strong>Created:</strong> ' + (query.createdDate ? new Date(query.createdDate).toLocaleDateString() : 'Unknown')));
$content.append($('<p>').html('<strong>Modified:</strong> ' + (query.lastModifiedDate ? new Date(query.lastModifiedDate).toLocaleDateString() : 'Unknown')));
$content.append($('<p>').html('<strong>Created By:</strong> ' + (query.createdBy ? query.createdBy.displayName : 'Unknown')));
$content.append($('<p>').html('<strong>Modified By:</strong> ' + (query.lastModifiedBy ? query.lastModifiedBy.displayName : 'Unknown')));
if (query.queryType) {
$content.append($('<p>').html('<strong>Type:</strong> ' + query.queryType));
}
// Assemble lightbox
$lightbox.append($closeBtn);
$lightbox.append($content);
$overlay.append($lightbox);
// Add to document and show
$('body').append($overlay);
// Close on overlay click
$overlay.on('click', function(e) {
if (e.target === $overlay[0]) {
$overlay.remove();
}
});
// Close on Escape key
$(document).on('keydown.lightbox', function(e) {
if (e.keyCode === 27) { // Escape key
$overlay.remove();
$(document).off('keydown.lightbox');
}
});
}
Adicionar estilo de caixa de luz
Inclua estilos CSS para a caixa de luz na seção HTML <head> do widget:
<style>
.query-summary {
list-style: none;
padding: 0;
margin: 10px 0;
}
.query-summary li {
padding: 2px 0;
font-size: 12px;
}
.details-link {
background: #0078d4;
color: white;
border: none;
padding: 4px 8px;
font-size: 11px;
cursor: pointer;
border-radius: 2px;
margin-top: 8px;
}
.details-link:hover {
background: #106ebe;
}
.lightbox-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
}
.lightbox-content {
background: white;
border-radius: 4px;
padding: 20px;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
position: relative;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.lightbox-close {
position: absolute;
top: 10px;
right: 15px;
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #666;
line-height: 1;
}
.lightbox-close:hover {
color: #000;
}
.query-details h3 {
margin-top: 0;
color: #323130;
}
.query-details p {
margin: 8px 0;
font-size: 14px;
line-height: 1.4;
}
</style>
Implementação avançada do widget
Seu widget aprimorado completo com funcionalidade de lightbox:
<!DOCTYPE html>
<html>
<head>
<script src="sdk/scripts/VSS.SDK.min.js"></script>
<style>
/* Lightbox styles from above */
.query-summary {
list-style: none;
padding: 0;
margin: 10px 0;
}
.query-summary li {
padding: 2px 0;
font-size: 12px;
}
.details-link {
background: #0078d4;
color: white;
border: none;
padding: 4px 8px;
font-size: 11px;
cursor: pointer;
border-radius: 2px;
margin-top: 8px;
}
.details-link:hover {
background: #106ebe;
}
.lightbox-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
}
.lightbox-content {
background: white;
border-radius: 4px;
padding: 20px;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
position: relative;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.lightbox-close {
position: absolute;
top: 10px;
right: 15px;
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #666;
line-height: 1;
}
.lightbox-close:hover {
color: #000;
}
.query-details h3 {
margin-top: 0;
color: #323130;
}
.query-details p {
margin: 8px 0;
font-size: 14px;
line-height: 1.4;
}
</style>
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers", "AzureDevOps/WorkItemTracking/RestClient"],
function (WidgetHelpers, WorkItemTrackingRestClient) {
WidgetHelpers.IncludeWidgetStyles();
function showQueryDetails(query) {
// Lightbox implementation from above
}
VSS.register("HelloWorldWidget2", function () {
var projectId = VSS.getWebContext().project.id;
var getQueryInfo = function (widgetSettings) {
return WorkItemTrackingRestClient.getClient().getQuery(projectId, "Shared Queries/Feedback")
.then(function (query) {
// Enhanced display with lightbox trigger
var $list = $('<ul class="query-summary">');
$list.append($('<li>').text("Query ID: " + query.id));
$list.append($('<li>').text("Query Name: " + query.name));
$list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName : "<unknown>")));
var $detailsLink = $('<button class="details-link">View Details</button>');
$detailsLink.on('click', function() {
showQueryDetails(query);
});
var $container = $('#query-info-container');
$container.empty();
$container.append($list);
$container.append($detailsLink);
return WidgetHelpers.WidgetStatusHelper.Success();
}, function (error) {
return WidgetHelpers.WidgetStatusHelper.Failure(error.message);
});
}
return {
load: function (widgetSettings) {
// Set your title
var $title = $('h2.title');
$title.text('Hello World');
return getQueryInfo(widgetSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
</head>
<body>
<div class="widget">
<h2 class="title"></h2>
<div id="query-info-container"></div>
</div>
</body>
</html>
Considerações de acessibilidade: verifique se sua caixa de luz está acessível para teclado e inclui rótulos adequados para leitores de tela. Teste com os recursos de acessibilidade internos do Azure DevOps.
Importante
Desempenho: as lightboxes devem carregar rapidamente. Considere carregar os dados detalhados sob demanda quando a lightbox for aberta, em vez de buscar tudo antecipadamente.
Etapa 5: Configurar o manifesto da extensão
Registre o widget configurável e sua interface de configuração no manifesto da extensão.
Adicionar widget e contribuições de configuração
Atualize vss-extension.json para incluir duas novas contribuições:
{
"contributions": [
{
"id": "HelloWorldWidget3",
"type": "ms.vss-dashboards-web.widget",
"targets": [
"ms.vss-dashboards-web.widget-catalog",
"fabrikam.azuredevops-extensions-myExtensions.HelloWorldWidget.Configuration"
],
"properties": {
"name": "Hello World Widget 3 (with config)",
"description": "My third widget",
"previewImageUrl": "img/preview3.png",
"uri": "hello-world3.html",
"supportedSizes": [
{
"rowSpan": 1,
"columnSpan": 2
},
{
"rowSpan": 2,
"columnSpan": 2
}
],
"supportedScopes": ["project_team"]
}
},
{
"id": "HelloWorldWidget.Configuration",
"type": "ms.vss-dashboards-web.widget-configuration",
"targets": [ "ms.vss-dashboards-web.widget-configuration" ],
"properties": {
"name": "HelloWorldWidget Configuration",
"description": "Configures HelloWorldWidget",
"uri": "configuration.html"
}
}
],
"files": [
{
"path": "hello-world.html", "addressable": true
},
{
"path": "hello-world2.html", "addressable": true
},
{
"path": "hello-world3.html", "addressable": true
},
{
"path": "configuration.html", "addressable": true
},
{
"path": "sdk/scripts", "addressable": true
},
{
"path": "img", "addressable": true
}
]
}
Requisitos para contribuição de configuração
| Propriedade | Propósito | Valor necessário |
|---|---|---|
type |
Identifica a contribuição como configuração de widget | ms.vss-dashboards-web.widget-configuration |
targets |
Onde a configuração é exibida | ms.vss-dashboards-web.widget-configuration |
uri |
Caminho para o arquivo HTML de configuração | Seu caminho de arquivo de configuração |
Padrão de direcionamento de widget
Para widgets configuráveis, a targets matriz deve incluir uma referência à configuração:
<publisher>.<extension-id>.<configuration-id>
Aviso
Visibilidade do botão de configuração: se o widget não direcionar corretamente sua contribuição de configuração, o botão Configurar não será exibido. Verifique se os nomes do editor e da extensão correspondem exatamente aos do seu manifesto.
Etapa 6: Empacotar, publicar e compartilhar
Implante sua extensão aprimorada com recursos de configuração.
Se for sua primeira publicação, siga a Etapa 6: Empacotar, publicar e compartilhar. Para extensões existentes, reempacote e atualize diretamente no Marketplace.
Etapa 7: Testar o widget configurável
Experimente o fluxo de trabalho de configuração completo adicionando e configurando seu widget.
Adicionar o widget ao painel
- Acesse
https://dev.azure.com/{Your_Organization}/{Your_Project}. - Vá para Visão Geral>Painéis.
- Selecione Adicionar um widget.
- Localize "Hello World Widget 3 (com configuração)" e selecione Adicionar.
Um prompt de configuração é exibido, pois o widget requer configuração:
Configurar o widget
Acessar a configuração por meio de qualquer método:
- Menu do widget: passe o cursor sobre o widget, selecione a reticência (⋯) e depois clique em Configurar
- Modo de edição do painel: Selecione Editar no painel e, em seguida, o botão configurar no widget
O painel de configuração abre com uma pré-visualização em tempo real no centro. Selecione uma consulta no menu suspenso para ver atualizações imediatas, depois selecione Salvar para aplicar suas alterações.
Etapa 8: Adicionar opções de configuração avançadas
Estenda o widget com recursos de configuração mais internos, como nomes e tamanhos personalizados.
Habilitar configuração de nome e tamanho
O Azure DevOps fornece dois recursos configuráveis prontos para uso:
| Característica | Propriedade de manifesto | Propósito |
|---|---|---|
| Nomes personalizados | isNameConfigurable: true |
Os usuários podem substituir o nome do widget padrão |
| Vários tamanhos |
supportedSizes Várias entradas |
Os usuários podem redimensionar widgets |
Exemplo de manifesto aprimorado
{
"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"]
}
}
]
}
Exibir nomes configurados
Para mostrar nomes de widget personalizados, atualize o widget para usar widgetSettings.name:
return {
load: function (widgetSettings) {
// Display configured name instead of hard-coded text
var $title = $('h2.title');
$title.text(widgetSettings.name);
return getQueryInfo(widgetSettings);
},
reload: function (widgetSettings) {
// Update name during configuration changes
var $title = $('h2.title');
$title.text(widgetSettings.name);
return getQueryInfo(widgetSettings);
}
}
Depois de atualizar sua extensão, você pode configurar o nome e o tamanho do widget:
Reempacote e atualize sua extensão para habilitar essas opções de configuração avançadas.
Parabéns! Você criou um widget de painel completo e configurável do Azure DevOps com recursos de visualização ao vivo e opções de personalização do usuário.