Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Serviços de DevOps do Azure | 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 widgets. Este artigo mostra como criar uma extensão que fornece um ou mais widgets.
Gorjeta
Confira a nossa documentação mais recente sobre desenvolvimento de extensões usando o SDK de Extensão Azure DevOps.
Gorjeta
Se você estiver iniciando uma nova extensão do Azure DevOps, experimente essas coleções de exemplo mantidas primeiro — elas funcionam com compilações 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 inicial compacto que demonstra padrões de extensão comuns: https://github.com/microsoft/azure-devops-extension-sample
- Exemplos de extensão do Azure DevOps (coleções herdadas e guia de contribuições) — instale para inspecionar alvos da interface do utilizador 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 (procure exemplos de DevOps do Azure) — exemplos selecionados e atualizados na documentação 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 e as versões de API do manifesto de extensão com os documentos 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 widgets |
| Organização do Azure DevOps | Criar uma organização |
| Editor de texto | Usamos o Visual Studio Code para tutoriais |
| Node.js | Última versão do Node.js |
| CLI multiplataforma |
TFX-CLI para empacotar extensões Instale usando: npm i -g tfx-cli |
| Diretório de projetos | Diretório inicial com esta 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 |
Descrição geral dos tutoriais
Este tutorial ensina o desenvolvimento de widgets através de três exemplos progressivos:
| Parte | Foco | O que você aprende |
|---|---|---|
| Parte 1: Olá Mundo | Criação básica de widgets | Criar um widget que exiba texto |
| Parte 2: Integração da API REST | Chamadas da 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 seu widget |
Gorjeta
Se você preferir ir direto para exemplos de trabalho, os exemplos incluídos (veja a nota anterior) mostram um conjunto de widgets que você pode empacotar e publicar.
Antes de começar, revise os estilos básicos de widgets e as orientações estruturais que fornecemos.
Parte 1: Olá Mundo
Crie um widget básico que exiba "Hello World" usando JavaScript. Esta base demonstra os principais conceitos de desenvolvimento de widgets.
Etapa 1: Instalar o SDK do cliente
O VSS SDK permite que seu widget se comunique com o Azure DevOps. Instale-o usando npm:
npm install vss-web-extension-sdk
Copie o ficheiro 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 GitHub do SDK do cliente.
Etapa 2: Criar a estrutura HTML
Crie hello-world.html no diretório do seu projeto. Este arquivo fornece o layout do widget e 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 JavaScript widget
Para implementar a funcionalidade do widget, adicione este script à <head> seção do seu 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 as bibliotecas SDK necessárias e os auxiliares de widgets |
VSS.register() |
Registra seu widget com um identificador exclusivo |
WidgetHelpers.IncludeWidgetStyles() |
Aplica o estilo padrão do Azure DevOps |
VSS.notifyLoadSucceeded() |
Notifica o framework de 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 a sua extensão:
-
Logotipo da
logo.pngextensão: imagem de 98x98 pixels nomeadaimgna pasta -
Ícone do catálogo de widgets: imagem de 98x98 pixels nomeada
CatalogIcon.pngna pastaimg -
Visualização do widget: imagem de 330x160 pixels nomeada
preview.pngna pastaimg
Essas imagens são exibidas no Marketplace e no catálogo de widgets quando os usuários navegam pelas extensões disponíveis.
Etapa 5: Criar o manifesto da extensão
Crie vss-extension.json no diretório raiz do seu projeto. Este arquivo define os metadados e contribuições da sua 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ção básica | 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 |
| Ficheiros | Todos os arquivos a serem incluídos no pacote de extensão |
Para obter a documentação completa do manifesto, consulte Referência do manifesto de 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
Crie seu pacote de extensão
No diretório do projeto, execute:
tfx extension create --manifest-globs vss-extension.json
Esta ação cria um .vsix arquivo que contém sua extensão empacotada.
Configurar um publicador
- Vá para o Portal de Publicação do Visual Studio Marketplace.
- Inicie sessão e crie um publicador, se não tiver um.
- Escolha um identificador de editor exclusivo (usado no seu arquivo de manifesto).
- Atualize o seu
vss-extension.jsonpara substituir "fabrikam" pelo nome do seu editor.
Carregue a sua extensão
- No Portal de Publicação, selecione Carregar nova extensão.
- Escolha o seu
.vsixficheiro 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
Gorjeta
Use --rev-version para incrementar automaticamente o número da versão ao atualizar uma extensão existente.
Etapa 7: Instalar e testar seu widget
Para testar, adicione seu widget a um painel:
- Vá para seu projeto de DevOps do Azure:
https://dev.azure.com/{Your_Organization}/{Your_Project}. - Vá para Visão geral>Dashboards.
- Selecione Adicionar um widget.
- Encontre seu widget no catálogo e selecione Adicionar.
Seu widget "Hello World" aparece no painel, exibindo o texto que você configurou.
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 seu widget para interagir com os dados do Azure DevOps usando APIs REST. Este exemplo demonstra como buscar informações de consulta e exibi-las dinamicamente em seu widget.
Nesta parte, use a API REST de Controle de Item de Trabalho para recuperar informações sobre uma consulta existente e exibir os detalhes da consulta abaixo do texto "Hello World".
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 seu 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. -
Atualizar o identificador do widget: altere o nome do widget de
HelloWorldWidgetpara paraHelloWorldWidget2para identificação exclusiva.
<!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 do trabalho
O vso.work escopo concede acesso somente leitura a itens de trabalho e consultas. Adicione este âmbito ao seu 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á publicou sua extensão, você deve removê-la do Marketplace primeiro. Vá para 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 JavaScript por meio do SDK. Essas bibliotecas encapsulam chamadas AJAX e mapeiam respostas de API para objetos utilizáveis.
Atualizar o JavaScript do widget
Substitua a VSS.require chamada em seu hello-world2.html para incluir o cliente REST de rastreamento de item 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 da implementação
| Componente | Propósito |
|---|---|
WorkItemTrackingRestClient.getClient() |
Obtém uma instância do cliente REST de Rastreamento de Item de Trabalho |
getQuery() |
Recupera informações de consulta encapsuladas em uma promessa |
WidgetStatusHelper.Failure() |
Fornece um tratamento consistente de erros para falhas de widgets. |
projectId |
Contexto de projeto atual necessário para chamadas de API |
Gorjeta
Caminhos de consulta personalizados: Se você não tiver uma consulta "Feedback" em "Consultas compartilhadas", substitua "Shared Queries/Feedback" pelo caminho para qualquer consulta existente 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 informações principais abaixo do texto "Hello World".
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 de widgets, adicione seu novo widget ao manifesto da extensão.
Adicionar a segunda contribuição do widget
Atualize vss-extension.json para incluir seu widget habilitado para API REST. Adicione esta 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"
]
}
Gorjeta
Visualizar imagem: crie uma preview2.png imagem (330x160 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
Empacote, publique e partilhe a sua extensão. Se você já publicou a extensão, pode reempacotá-la e atualizá-la diretamente no Marketplace.
Etapa 7: Testar o widget da API REST
Para visualizar a integração da API REST em ação, adicione o novo widget ao seu painel:
- Vá para seu projeto de DevOps do Azure:
https://dev.azure.com/{Your_Organization}/{Your_Project}. - Selecione Visão Geral>.
- Selecione Adicionar um widget.
- Encontre "Hello World Widget 2 (with API)" e selecione Adicionar.
Seu widget aprimorado exibe o texto "Hello World" 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 permitam aos usuários personalizar qual consulta exibir.
Parte 3: Configurar o Hello World
Desenvolva a Parte 2 adicionando recursos de configuração do usuário ao seu widget. Em vez de codificar o caminho da consulta, crie uma interface de configuração que permita aos usuários selecionar qual consulta exibir, com a funcionalidade de visualização ao vivo.
Esta parte demonstra como criar widgets configuráveis que os usuários podem personalizar de acordo com suas necessidades específicas, fornecendo feedback em tempo real durante a configuração.
Etapa 1: Criar arquivos de configuração
As configurações de widgets compartilham muitas semelhanças com os próprios widgets — ambos usam o mesmo SDK, estrutura HTML e padrões JavaScript, mas servem a propósitos diferentes dentro da estrutura de extensão.
Configurar a estrutura do projeto
Para suportar a configuração do widget, crie dois novos arquivos:
- Copie-o
hello-world2.htmle renomeie-o parahello-world3.html, seu widget configurável. - Crie um novo arquivo chamado
configuration.html, que lida com a interface de configuração.
A estrutura do seu 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 esta estrutura HTML ao configuration.html, que cria um menu suspenso para seleção 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 JavaScript de configuração
O JavaScript de configuração segue o mesmo padrão de inicialização dos 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 exige as seguintes funções essenciais:
| 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 configurações | Quando o usuário seleciona Salvar |
Gorjeta
Serialização de dados: Este exemplo usa JSON para serializar configurações. O widget acede a estas configurações através de widgetSettings.customSettings.data e deve desserializá-las de acordo.
Etapa 3: Ativar 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 definições de configuração, fornecendo feedback instantâneo 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);
});
Arquivo de configuração completo
O seu configuration.html final deve ter o seguinte aspeto:
<!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 Ativar 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 change garante que essa ação ocorra quando os usuários selecionam uma opção.
Etapa 4: Tornar o widget configurável
Transforme seu widget da Parte 2 para usar dados de configuração em vez de valores codificados. Esta etapa requer a implementação do IConfigurableWidget contrato.
Atualizar registro do widget
No hello-world3.html, faça estas alterações:
-
Atualizar ID do widget: Mude de
HelloWorldWidget2paraHelloWorldWidget3. -
Adicionar função de recarga: Implemente o
IConfigurableWidgetcontrato.
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 getQueryInfo função para usar definições de configuração em vez de caminhos de consulta codificados:
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 nova configuração | Atualizações leves, atualização de dados |
Gorjeta
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 lightbox para obter informações detalhadas
Os widgets do painel têm espaço limitado, o que dificulta a exibição de informações abrangentes. Uma lightbox fornece uma solução elegante, mostrando dados detalhados em uma sobreposição modal sem sair do dashboard.
Por que usar uma lightbox em widgets?
| Benefício | Descrição |
|---|---|
| Eficiência do espaço | Mantenha o widget compacto enquanto oferece vistas detalhadas |
| Experiência do utilizador | Manter o contexto do painel enquanto mostra mais informações |
| Divulgação progressiva | Mostrar dados de resumo no widget, detalhes sob demanda |
| Design responsivo | Adapte-se a diferentes tamanhos de tela e configurações de widgets |
Implementar elementos clicáveis
Atualize a renderização de dados de consulta para incluir elementos clicáveis que acionam a lightbox:
// 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 de visualização em destaque (lightbox)
Adicione esta implementação 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 lightbox
Inclua estilos CSS para a lightbox 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 aprimorada de widgets
Seu widget aprimorado completo com funcionalidade 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 sobre acessibilidade: Certifique-se de que a sua lightbox é acessível pelo teclado e inclui etiquetas adequadas para leitores de ecrã. Teste com os recursos de acessibilidade internos do Azure DevOps.
Importante
Desempenho: As caixas de luz devem ser carregadas rapidamente. Considere carregar os dados detalhados usando lazy loading apenas quando a lightbox é 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 em seu manifesto de extensão.
Adicionar widget e contribuições de configuração
Atualização 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 de colaboração para configuração
| Propriedade | Propósito | Valor obrigatório |
|---|---|---|
type |
Identifica a contribuição como configuração do widget | ms.vss-dashboards-web.widget-configuration |
targets |
Onde a configuração aparece | ms.vss-dashboards-web.widget-configuration |
uri |
Caminho para o arquivo HTML de configuração | O caminho do arquivo de configuração |
Padrão de segmentação do 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 aparecerá. Verifique se os nomes do editor e da extensão correspondem exatamente ao seu manifesto.
Etapa 6: Empacotar, publicar e compartilhar
Implante sua extensão aprimorada com recursos de configuração.
Se for a sua primeira publicação, siga o Passo 6: Empacotar, publicar e partilhar. 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.
Adicione o widget ao seu painel
- Aceda a
https://dev.azure.com/{Your_Organization}/{Your_Project}. - Vá para Visão geral>Dashboards.
- Selecione Adicionar um widget.
- Encontre "Hello World Widget 3 (with config)" e selecione Adicionar.
Um prompt de configuração é exibido, já que o widget requer configuração:
Configurar o widget
Configuração de acesso através de qualquer um dos métodos:
- Menu do widget: passe o cursor sobre o widget, selecione as reticências (⋯) e, em seguida, configure
- 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-se com uma visualização ao vivo na parte central. Selecione uma consulta na lista suspensa para ver atualizações imediatas e, em seguida, selecione Salvar para aplicar as alterações.
Etapa 8: Adicionar opções de configuração avançadas
Estenda seu widget com mais recursos de configuração integrados, como nomes e tamanhos personalizados.
Ativar configuração de nome e tamanho
O Azure DevOps fornece dois recursos configuráveis prontos para uso:
| Característica | Propriedade do manifesto | Propósito |
|---|---|---|
| Nomes personalizados | isNameConfigurable: true |
Os usuários podem substituir o nome padrão do widget |
| Vários tamanhos | Entradas múltiplas supportedSizes |
Os usuários podem redimensionar widgets |
Exemplo de manifesto melhorado
{
"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 widgets personalizados, atualize seu 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 completo e configurável do painel do Azure DevOps com recursos de visualização ao vivo e opções de personalização do usuário.