Adicionar um widget de painel
Serviços de DevOps do Azure | Azure DevOps Server 2022 - Azure DevOps Server 2019
Widgets em um painel são implementados como contribuições na estrutura de extensão. Uma única extensão pode ter múltiplas contribuições. Saiba como criar uma extensão com vários widgets como contribuições.
Este artigo está dividido em três partes, cada uma com base na anterior - começando com um widget simples e terminando com um widget abrangente.
Gorjeta
Confira nossa documentação mais recente sobre desenvolvimento de extensões usando o SDK de Extensão do Azure DevOps.
Preparação e configuração necessária para este tutorial
Para criar extensões para o Azure DevOps ou TFS, há alguns softwares e ferramentas de pré-requisito de que você precisará:
Conhecimento: Algum conhecimento de JavaScript, HTML, CSS é necessário para o desenvolvimento de widgets.
- Uma organização no Azure DevOps para instalar e testar seu widget, mais informações podem ser encontradas aqui
- Um editor de texto. Para muitos dos tutoriais, usamos
Visual Studio Code
, que pode ser baixado aqui - A última versão do nó, que pode ser baixada aqui
- CLI de plataforma cruzada para Azure DevOps (tfx-cli) para empacotar suas extensões.
- tfx-cli pode ser instalado usando
npm
, um componente do Node.js executandonpm i -g tfx-cli
- tfx-cli pode ser instalado usando
- Um diretório base para o seu projeto. Este diretório é referido como
home
em todo o tutorial.
Estrutura do arquivo de extensão:
|--- 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
O que você encontrará no tutorial
- A primeira parte deste guia mostra como criar um novo widget, que imprime uma simples mensagem "Hello World".
- A segunda parte se baseia na primeira adicionando uma chamada a uma API REST do Azure DevOps.
- A terceira parte explica como adicionar configuração ao seu widget.
Nota
Se você estiver com pressa e quiser colocar as mãos no código imediatamente, você pode baixar os exemplos aqui.
Uma vez baixado, vá para a pasta e, em seguida, siga a Etapa 6 e a Etapa 7 diretamente para publicar a widgets
extensão de exemplo que tem os três widgets de exemplo de complexidades variadas.
Comece com alguns estilos básicos para widgets que fornecemos prontos para você e algumas orientações sobre a estrutura do widget.
Parte 1: Olá Mundo
Esta parte apresenta um widget que imprime "Hello World" usando JavaScript.
Etapa 1: Obter o SDK do cliente - VSS.SDK.min.js
O script SDK principal, , VSS.SDK.min.js
permite que as extensões da Web se comuniquem com o quadro do Azure DevOps do host. O script faz operações como inicializar, notificar que a extensão está carregada ou obter contexto sobre a página atual.
Obtenha o arquivo SDK VSS.SDK.min.js
do cliente e adicione-o ao seu aplicativo Web. Coloque-o home/sdk/scripts
na pasta.
Use o comando 'npm install' para recuperar o SDK:
npm install vss-web-extension-sdk
Para saber mais sobre o SDK, visite a página do GitHub do SDK do cliente.
Passo 2: A sua página HTML - hello-world.html
Sua página HTML é a cola que mantém seu layout unido e inclui referências a CSS e JavaScript.
Você pode nomear este arquivo qualquer coisa, apenas certifique-se de atualizar todas as referências com o nome que hello-world
você usa.
Seu widget é baseado em HTML e está hospedado em um iframe.
Adicione o HTML abaixo em hello-world.html
. Adicionamos a referência obrigatória ao VSS.SDK.min.js
arquivo e incluímos um h2
elemento no , que é atualizado com a string Hello World na próxima etapa.
<!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>
Mesmo que estejamos usando um arquivo HTML, a maioria dos elementos de cabeçalho HTML diferentes de script e link são ignorados pela estrutura.
Passo 3: O seu JavaScript
Usamos JavaScript para renderizar conteúdo no widget. Neste artigo, encapsulamos todo o nosso código JavaScript dentro de um <script>
elemento no arquivo HTML. Você pode optar por ter esse código em um arquivo JavaScript separado e consultá-lo no arquivo HTML.
O código renderiza o conteúdo. Esse código JavaScript também inicializa o SDK do VSS, mapeia o código do widget para o nome do widget e notifica a estrutura de extensão sobre sucessos ou falhas do widget.
No nosso caso, abaixo está o código que imprimiria "Hello World" no widget. Adicione este script
elemento no head
do 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 o handshake entre o iframe que hospeda o widget e o quadro do host.
Passamos explicitNotifyLoaded: true
para que o widget possa notificar explicitamente o host quando terminarmos de carregar. Este controlo permite-nos notificar a conclusão da carga depois de garantir que os módulos dependentes estão carregados.
Passamos usePlatformStyles: true
para que os estilos principais do Azure DevOps para elementos html (como body, div e assim por diante) possam ser usados pelo Widget. Se o widget preferir não usar esses estilos, eles podem passar .usePlatformStyles: false
VSS.require
é usado para carregar as bibliotecas de scripts VSS necessárias. Uma chamada para esse método carrega automaticamente bibliotecas gerais como JQuery e JQueryUI.
No nosso caso, dependemos da biblioteca WidgetHelpers, que é usada para comunicar o status do widget para a estrutura do widget.
Assim, passamos o nome TFS/Dashboards/WidgetHelpers
do módulo correspondente e um retorno de chamada para VSS.require
.
O retorno de chamada é chamado assim que o módulo é carregado.
O retorno de chamada tem o resto do código JavaScript necessário para o widget. No final do retorno de chamada, ligamos VSS.notifyLoadSucceeded
para notificar a conclusão da carga.
WidgetHelpers.IncludeWidgetStyles
Inclui uma folha de estilo com alguns CSS básicos para você começar. Certifique-se de envolver seu conteúdo dentro de um elemento HTML com classe widget
para fazer uso desses estilos.
VSS.register
é usado para mapear uma função em JavaScript, que identifica exclusivamente o widget entre as diferentes contribuições em sua extensão. O nome deve corresponder ao que identifica a sua contribuição, id
conforme descrito no Passo 5. Para widgets, a função para a qual é passada VSS.register
deve retornar um objeto que satisfaça o contrato, por exemplo, o objeto retornado deve ter uma propriedade load cujo valor é outra função que tem a lógica central para renderizar o IWidget
widget.
No nosso caso, é atualizar o h2
texto do elemento para "Hello World".
É essa função que é chamada quando a estrutura do widget instancia seu widget.
Usamos o de WidgetHelpers para retornar o WidgetStatusHelper
WidgetStatus
como sucesso.
Aviso
Se o nome usado para registrar o widget não corresponder ao ID da contribuição no manifesto, o widget funcionará inesperadamente.
O vss-extension.json
deve estar sempre na raiz da pasta (neste guia, HelloWorld
). Para todos os outros arquivos, você pode colocá-los em qualquer estrutura que você quiser dentro da pasta, apenas certifique-se de atualizar as referências adequadamente nos arquivos HTML e no manifesto vss-extension.json
.
Passo 4: O logótipo da sua extensão: logo.png
Seu logotipo é exibido no Marketplace e no catálogo de widgets assim que um usuário instala sua extensão.
Você precisa de um ícone de catálogo de 98 px x 98 px. Escolha uma imagem, nomeie-a e coloque-a logo.png
img
na pasta.
Para suportar o TFS 2015 Update 3, você precisa de uma imagem adicional de 330 px x 160 px. Esta imagem de pré-visualização é mostrada neste catálogo. Escolha uma imagem, nomeie-a e coloque-a preview.png
img
na pasta como antes.
Você pode nomear essas imagens como quiser, desde que o manifesto da extensão na próxima etapa seja atualizado com os nomes usados.
Passo 5: O manifesto da sua extensão: vss-extension.json
- Cada extensão deve ter um arquivo de manifesto de extensão
- Leia a referência do manifesto de extensão
- Saiba mais sobre os pontos de contribuição em Pontos de extensibilidade
Crie um arquivo json (vss-extension.json
, por exemplo) no home
diretório com o seguinte conteúdo:
{
"manifestVersion": 1,
"id": "vsts-extensions-myExtensions",
"version": "1.0.0",
"name": "My First Set of Widgets",
"description": "Samples containing different widgets extending dashboards",
"publisher": "fabrikam",
"categories": ["Azure Boards"],
"targets": [
{
"id": "Microsoft.VisualStudio.Services"
}
],
"icons": {
"default": "img/logo.png"
},
"contributions": [
{
"id": "HelloWorldWidget",
"type": "ms.vss-dashboards-web.widget",
"targets": [
"ms.vss-dashboards-web.widget-catalog"
],
"properties": {
"name": "Hello World Widget",
"description": "My first widget",
"catalogIconUrl": "img/CatalogIcon.png",
"previewImageUrl": "img/preview.png",
"uri": "hello-world.html",
"supportedSizes": [
{
"rowSpan": 1,
"columnSpan": 2
}
],
"supportedScopes": ["project_team"]
}
}
],
"files": [
{
"path": "hello-world.html", "addressable": true
},
{
"path": "sdk/scripts", "addressable": true
},
{
"path": "img", "addressable": true
}
]
}
Para obter mais informações sobre os atributos necessários, consulte a referência do manifesto de extensão
Nota
O editor aqui precisa ser alterado para o nome do editor. Para criar um editor agora, visite Pacote/Publicar/Instalar.
Ícones
A sub-rotina de ícones especifica o caminho para o ícone da extensão no manifesto.
Contribuições
Cada entrada de contribuição define propriedades.
- O ID para identificar a sua contribuição. Esse ID deve ser exclusivo dentro de uma extensão. Esse ID deve corresponder ao nome que você usou na Etapa 3 para registrar seu widget.
- O tipo de contribuição. Para todos os widgets, o tipo deve ser
ms.vss-dashboards-web.widget
. - O conjunto de objetivos para os quais a contribuição está a contribuir. Para todos os widgets, o alvo deve ser
[ms.vss-dashboards-web.widget-catalog]
. - As propriedades são objetos que incluem propriedades para o tipo de contribuição. Para widgets, as seguintes propriedades são obrigatórias.
Property | Descrição |
---|---|
nome | Nome do widget a ser exibido no catálogo de widgets. |
descrição | Descrição do widget a ser exibido no catálogo de widgets. |
catalogIconUrl | Caminho relativo do ícone de catálogo que você adicionou na Etapa 4 para exibir no catálogo de widgets. A imagem deve ser 98 px x 98 px. Se você usou uma estrutura de pastas diferente ou um nome de arquivo diferente, especifique o caminho relativo apropriado aqui. |
pré-visualizaçãoImageUrl | Caminho relativo da imagem de visualização que você adicionou na Etapa 4 para exibir no catálogo de widgets somente para TFS 2015 Atualização 3. A imagem deve ser 330 px x 160 px. Se você usou uma estrutura de pastas diferente ou um nome de arquivo diferente, especifique o caminho relativo apropriado aqui. |
uri | Caminho relativo do arquivo HTML que você adicionou na Etapa 1. Se você usou uma estrutura de pastas diferente ou um nome de arquivo diferente, especifique o caminho relativo apropriado aqui. |
supportedSizes | Matriz de tamanhos suportados pelo seu widget. Quando um widget suporta vários tamanhos, o primeiro tamanho na matriz é o tamanho padrão do widget. O widget size é especificado para as linhas e colunas ocupadas pelo widget na grade do painel. Uma linha/coluna corresponde a 160 px. Qualquer dimensão acima de 1x1 recebe 10 px adicionais que representam a calha entre widgets. Por exemplo, um widget 3x2 é 160*3+10*2 largo e 160*2+10*1 alto. O tamanho máximo suportado é 4x4 . |
supportedScopes | No momento, suportamos apenas painéis de equipe. O valor tem de ser project_team . No futuro, quando oferecermos suporte a outros escopos de painel, haverá mais opções para escolher aqui. |
Files
A sub-rotina de arquivos indica os arquivos que você deseja incluir em seu pacote - sua página HTML, seus scripts, o script SDK e seu logotipo.
Defina addressable
como a true
menos que você inclua outros arquivos que não precisam ser endereçáveis por URL.
Nota
Para obter mais informações sobre o arquivo de manifesto de extensão, como suas propriedades e o que eles fazem, confira a referência de manifesto de extensão.
Etapa 6: Empacotar, publicar e compartilhar
Depois de escrever sua extensão, o próximo passo para colocá-la no Marketplace é empacotar todos os seus arquivos juntos. Todas as extensões são empacotadas como arquivos .vsix compatíveis com VSIX 2.0 - a Microsoft fornece uma interface de linha de comando (CLI) multiplataforma para empacotar sua extensão.
Obtenha a ferramenta de embalagem
Você pode instalar ou atualizar a CLI de plataforma cruzada para DevOps do Azure (tfx-cli) usando npm
o , um componente do Node.js, a partir da sua linha de comando.
npm i -g tfx-cli
Empacote sua extensão
Empacotar sua extensão em um arquivo .vsix é fácil depois de ter o tfx-cli. Vá para o diretório inicial da sua extensão e execute o seguinte comando.
tfx extension create --manifest-globs vss-extension.json
Nota
A versão de uma extensão/integração deve ser incrementada em cada atualização.
Ao atualizar uma extensão existente, atualize a versão no manifesto ou passe a opção de linha de --rev-version
comando. Isso incrementa o número da versão do patch da sua extensão e salva a nova versão no seu manifesto.
Depois de ter sua extensão empacotada em um arquivo .vsix, você estará pronto para publicar sua extensão no Marketplace.
Criar editor para a extensão
Todas as extensões, incluindo extensões da Microsoft, são identificadas como sendo fornecidas por um editor. Se você ainda não for membro de um editor existente, criará um.
- Entre no Portal de Publicação do Visual Studio Marketplace
- Se ainda não for membro de um editor existente, ser-lhe-á pedido para criar um editor. Se você não for solicitado a criar um editor, role para baixo até a parte inferior da página e selecione Publicar extensões abaixo de Sites relacionados.
- Especifique um identificador para o editor, por exemplo:
mycompany-myteam
- O identificador é usado como o valor para o atributo no arquivo de manifesto
publisher
de suas extensões.
- O identificador é usado como o valor para o atributo no arquivo de manifesto
- Especifique um nome para exibição para seu editor, por exemplo:
My Team
- Especifique um identificador para o editor, por exemplo:
- Reveja o Contrato de Editor do Marketplace e selecione Criar
Agora o seu editor está definido. Em uma versão futura, você pode conceder permissões para exibir e gerenciar as extensões do editor. É fácil e mais seguro para equipes e organizações publicar extensões em um editor comum, mas sem a necessidade de compartilhar um conjunto de credenciais em um conjunto de usuários.
Atualize o arquivo de manifesto nos exemplos para substituir o vss-extension.json
ID fabrikam
do editor fictício pelo ID do editor.
Publicar e compartilhar a extensão
Depois de criar um editor, agora você pode carregar sua extensão para o Marketplace.
- Encontre o botão Carregar nova extensão , navegue até o arquivo .vsix empacotado e selecione carregar.
Você também pode carregar sua extensão através da linha de comando usando o tfx extension publish
comando em vez de tfx extension create
empacotar e publicar sua extensão em uma etapa.
Opcionalmente, você pode usar --share-with
para compartilhar sua extensão com uma ou mais contas após a publicação.
Você também precisará de um token de acesso pessoal.
tfx extension publish --manifest-globs your-manifest.json --share-with yourOrganization
Etapa 7: Adicionar widget do catálogo
Vá para o seu projeto no Azure DevOps,
http://dev.azure.com/{yourOrganization}/{yourProject}
Selecione Visão geral e, em seguida, selecione Painéis.
Escolha Adicionar um widget.
Realce o widget e selecione Adicionar.
O widget aparece no seu painel.
Parte 2: Olá Mundo com a API REST do Azure DevOps
Os widgets podem chamar qualquer uma das APIs REST no Azure DevOps para interagir com os recursos do Azure DevOps. Neste exemplo, usamos a API REST para WorkItemTracking para buscar informações sobre uma consulta existente e exibir algumas informações de consulta no widget logo abaixo do texto "Hello World".
Passo 1: HTML
Copie o arquivo hello-world.html
do exemplo anterior e renomeie a cópia para hello-world2.html
. Sua pasta agora se parece com abaixo:
|--- README.md
|--- sdk
|--- node_modules
|--- scripts
|--- VSS.SDK.min.js
|--- img
|--- logo.png
|--- scripts
|--- hello-world.html // html page to be used for your widget
|--- hello-world2.html // renamed copy of hello-world.html
|--- vss-extension.json // extension's manifest
Adicione um novo elemento 'div' logo abaixo de 'h2' para armazenar as informações da consulta. Atualize o nome do widget de 'HelloWorldWidget' para 'HelloWorldWidget2' na linha onde você chama 'VSS.register'. Isso permite que a estrutura identifique exclusivamente o widget dentro da extensão.
<!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>
Etapa 2: Acessar os recursos do Azure DevOps
Para habilitar o acesso aos recursos do Azure DevOps, os escopos precisam ser especificados no manifesto de extensão. Acrescentamos o vso.work
âmbito ao nosso manifesto.
Esse escopo indica que o widget precisa de acesso somente leitura a consultas e itens de trabalho. Veja todos os escopos disponíveis aqui.
Adicione o abaixo no final do manifesto de extensão.
{
...,
"scopes":[
"vso.work"
]
}
Aviso
No momento, não há suporte para adicionar ou alterar escopos após a publicação de uma extensão. Se já carregou a sua extensão, remova-a do Marketplace. Vá para , clique com o botão direito do mouse em sua extensão e selecione "Remover".
Etapa 3: Fazer a chamada à API REST
Há muitas bibliotecas do lado do cliente que podem ser acessadas por meio do SDK para fazer chamadas de API REST no Azure DevOps. Essas bibliotecas são chamadas de clientes REST e são wrappers JavaScript em torno de chamadas Ajax para todos os pontos de extremidade disponíveis do lado do servidor. Você pode usar métodos fornecidos por esses clientes em vez de escrever chamadas Ajax você mesmo. Esses métodos mapeiam as respostas da API para objetos que podem ser consumidos pelo seu código.
Nesta etapa, atualizamos a VSS.require
chamada para carregar TFS/WorkItemTracking/RestClient
, que fornece o cliente REST WorkItemTracking.
Podemos usar este cliente REST para obter informações sobre uma consulta chamada Feedback
sob a pasta Shared Queries
.
Dentro da função que passamos para , criamos uma variável para VSS.register
manter o ID do projeto atual. Precisamos dessa variável para buscar a consulta.
Também criamos um novo método getQueryInfo
para usar o cliente REST. Este método que é então chamado a partir do método load.
O método getClient
fornece uma instância do cliente REST de que precisamos.
O método getQuery
retorna a consulta encapsulada em uma promessa.
A aparência atualizada VSS.require
é a seguinte:
VSS.require(["TFS/Dashboards/WidgetHelpers", "TFS/WorkItemTracking/RestClient"],
function (WidgetHelpers, TFS_Wit_WebApi) {
WidgetHelpers.IncludeWidgetStyles();
VSS.register("HelloWorldWidget2", function () {
var projectId = VSS.getWebContext().project.id;
var getQueryInfo = function (widgetSettings) {
// Get a WIT client to make REST calls to Azure DevOps Services
return TFS_Wit_WebApi.getClient().getQuery(projectId, "Shared Queries/Feedback")
.then(function (query) {
// Do something with the query
return WidgetHelpers.WidgetStatusHelper.Success();
}, function (error) {
return WidgetHelpers.WidgetStatusHelper.Failure(error.message);
});
}
return {
load: function (widgetSettings) {
// Set your title
var $title = $('h2.title');
$title.text('Hello World');
return getQueryInfo(widgetSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
Observe o uso do método Failure de WidgetStatusHelper
.
Ele permite que você indique à estrutura do widget que ocorreu um erro e aproveite a experiência de erro padrão fornecida a todos os widgets.
Se você não tiver a
Feedback
consulta naShared Queries
pasta, substituaShared Queries\Feedback
no código pelo caminho de uma consulta que existe em seu projeto.
Etapa 4: Exibir a resposta
A última etapa é renderizar as informações da consulta dentro do widget.
A getQuery
função retorna um objeto do tipo Contracts.QueryHierarchyItem
dentro de uma promessa.
Neste exemplo, exibimos a ID da consulta, o nome da consulta e o nome do criador da consulta sob o texto "Hello World".
Substitua o // Do something with the query
comentário pelo seguinte:
// Create a list with query details
var $list = $('<ul>');
$list.append($('- ').text("Query Id: " + query.id));
$list.append($('- ').text("Query Name: " + query.name));
$list.append($('- ').text("Created By: " + ( query.createdBy? query.createdBy.displayName: "<unknown>" ) ) );
// Append the list to the query-info-container
var $container = $('#query-info-container');
$container.empty();
$container.append($list);
A sua final hello-world2.html
é a seguinte:
<!DOCTYPE html>
<html>
<head>
<script src="sdk/scripts/VSS.SDK.min.js"></script>
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["TFS/Dashboards/WidgetHelpers", "TFS/WorkItemTracking/RestClient"],
function (WidgetHelpers, TFS_Wit_WebApi) {
WidgetHelpers.IncludeWidgetStyles();
VSS.register("HelloWorldWidget2", function () {
var projectId = VSS.getWebContext().project.id;
var getQueryInfo = function (widgetSettings) {
// Get a WIT client to make REST calls to Azure DevOps Services
return TFS_Wit_WebApi.getClient().getQuery(projectId, "Shared Queries/Feedback")
.then(function (query) {
// Create a list with query details
var $list = $('<ul>');
$list.append($('- ').text("Query ID: " + query.id));
$list.append($('- ').text("Query Name: " + query.name));
$list.append($('- ').text("Created By: " + (query.createdBy ? query.createdBy.displayName: "<unknown>") ));
// Append the list to the query-info-container
var $container = $('#query-info-container');
$container.empty();
$container.append($list);
// Use the widget helper and return success as Widget Status
return WidgetHelpers.WidgetStatusHelper.Success();
}, function (error) {
// Use the widget helper and return failure as Widget Status
return WidgetHelpers.WidgetStatusHelper.Failure(error.message);
});
}
return {
load: function (widgetSettings) {
// Set your title
var $title = $('h2.title');
$title.text('Hello World');
return getQueryInfo(widgetSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
</head>
<body>
<div class="widget">
<h2 class="title"></h2>
<div id="query-info-container"></div>
</div>
</body>
</html>
Etapa 5: Atualizações do manifesto de extensão
Nesta etapa, atualizamos o manifesto da extensão para incluir uma entrada para nosso segundo widget.
Adicione uma nova contribuição à matriz na propriedade e adicione o novo arquivo hello-world2.html
à matriz na contributions
propriedade files.
Você precisa de outra imagem de visualização para o segundo widget. Nomeie-o preview2.png
e coloque-o img
na pasta.
{
...,
"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"
]
}
Etapa 6: Empacotar, publicar e compartilhar
Empacote, publique e compartilhe sua extensão. Se você já publicou a extensão, pode reempacotá-la e atualizá-la diretamente para o Marketplace.
Etapa 7: Adicionar widget do catálogo
Agora, vá para o painel da sua equipe em https:\//dev.azure.com/{yourOrganization}/{yourProject}
. Se esta página já estiver aberta, atualize-a.
Passe o cursor sobre o botão Editar no canto inferior direito e selecione o botão Adicionar. O catálogo de widgets é aberto onde você encontra o widget instalado.
Escolha o seu widget e selecione o botão 'Adicionar' para adicioná-lo ao seu painel.
Parte 3: Olá Mundo com Configuração
Na Parte 2 deste guia, você viu como criar um widget que mostra informações de consulta para uma consulta codificada. Nesta parte, adicionamos a capacidade de configurar a consulta a ser usada em vez da consulta codificada. Quando no modo de configuração, o usuário pode ver uma visualização ao vivo do widget com base em suas alterações. Essas alterações são salvas no widget no painel quando o usuário seleciona Salvar.
Passo 1: HTML
As implementações de Widgets e Configurações de Widgets são muito parecidas. Ambos são implementados no quadro de extensão como contribuições. Ambos usam o mesmo arquivo SDK, VSS.SDK.min.js
. Ambos são baseados em HTML, JavaScript e CSS.
Copie o arquivo html-world2.html
do exemplo anterior e renomeie a cópia para hello-world3.html
. Adicione outro arquivo HTML chamado configuration.html
.
Sua pasta agora se parece com o exemplo a seguir:
|--- 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
Adicione o HTML abaixo em 'configuração.html'. Basicamente, acrescentamos a referência obrigatória ao "VSS. SDK.min.js' e um elemento 'select' para a lista suspensa para selecionar uma consulta de uma lista predefinida.
<!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: JavaScript - Configuração
Use JavaScript para renderizar conteúdo na configuração do widget, assim como fizemos para o widget na Etapa 3 da Parte 1 deste guia.
Esse código JavaScript renderiza conteúdo, inicializa o SDK do VSS, mapeia o código da configuração do widget para o nome da configuração e passa as definições de configuração para a estrutura. No nosso caso, abaixo está o código que carrega a configuração do widget.
Abra o arquivo configuration.html
e o elemento abaixo <script>
no <head>
.
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require("TFS/Dashboards/WidgetHelpers", function (WidgetHelpers) {
VSS.register("HelloWorldWidget.Configuration", function () {
var $queryDropdown = $("#query-path-dropdown");
return {
load: function (widgetSettings, widgetConfigurationContext) {
var settings = JSON.parse(widgetSettings.customSettings.data);
if (settings && settings.queryPath) {
$queryDropdown.val(settings.queryPath);
}
return WidgetHelpers.WidgetStatusHelper.Success();
},
onSave: function() {
var customSettings = {
data: JSON.stringify({
queryPath: $queryDropdown.val()
})
};
return WidgetHelpers.WidgetConfigurationSave.Valid(customSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
VSS.init
, VSS.require
e VSS.register
desempenham o mesmo papel que desempenharam para o widget, conforme descrito na Parte 1.
A única diferença é que, para configurações de widget, a função para a qual é passada VSS.register
deve retornar um objeto que satisfaça o IWidgetConfiguration
contrato.
A load
propriedade do IWidgetConfiguration
contrato deve ter uma função como seu valor.
Esta função tem o conjunto de etapas para renderizar a configuração do widget.
No nosso caso, é atualizar o valor selecionado do elemento suspenso com as configurações existentes, se houver.
Esta função é chamada quando a estrutura instancia o seu widget configuration
A onSave
propriedade do IWidgetConfiguration
contrato deve ter uma função como seu valor.
Essa função é chamada pela estrutura quando o usuário seleciona Salvar no painel de configuração.
Se a entrada do usuário estiver pronta para salvar, serialize-a em uma cadeia de caracteres, forme o custom settings
objeto e use WidgetConfigurationSave.Valid()
para salvar a entrada do usuário.
Neste guia, usamos JSON para serializar a entrada do usuário em uma cadeia de caracteres. Você pode escolher qualquer outra maneira de serializar a entrada do usuário para string.
É acessível ao widget através da propriedade customSettings do WidgetSettings
objeto.
O widget tem que desserializar isso, que é abordado na Etapa 4.
Passo 3: JavaScript - Ativar Live Preview
Para habilitar a atualização de visualização ao vivo quando o usuário seleciona uma consulta na lista suspensa, anexamos um manipulador de eventos de alteração ao botão. Esse manipulador notifica a estrutura de que a configuração foi alterada.
Ele também passa o customSettings
para ser usado para atualizar a visualização. Para notificar o quadro, o método sobre o notify
widgetConfigurationContext
precisa ser chamado. Ele leva dois parâmetros, o nome do evento, que neste caso é WidgetHelpers.WidgetEvent.ConfigurationChange
, e um EventArgs
objeto para o evento, criado a partir do com a customSettings
ajuda do WidgetEvent.Args
método auxiliar.
Adicione o abaixo na função atribuída à load
propriedade.
$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);
});
Você precisa notificar a estrutura de alteração de configuração pelo menos uma vez para que o botão "Salvar" possa ser ativado.
No final, a sua configuration.html
aparência é assim:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="sdk/scripts/VSS.SDK.min.js"></script>
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require("TFS/Dashboards/WidgetHelpers", function (WidgetHelpers) {
VSS.register("HelloWorldWidget.Configuration", function () {
var $queryDropdown = $("#query-path-dropdown");
return {
load: function (widgetSettings, widgetConfigurationContext) {
var settings = JSON.parse(widgetSettings.customSettings.data);
if (settings && settings.queryPath) {
$queryDropdown.val(settings.queryPath);
}
$queryDropdown.on("change", function () {
var customSettings = {data: JSON.stringify({queryPath: $queryDropdown.val()})};
var eventName = WidgetHelpers.WidgetEvent.ConfigurationChange;
var eventArgs = WidgetHelpers.WidgetEvent.Args(customSettings);
widgetConfigurationContext.notify(eventName, eventArgs);
});
return WidgetHelpers.WidgetStatusHelper.Success();
},
onSave: function() {
var customSettings = {data: JSON.stringify({queryPath: $queryDropdown.val()})};
return WidgetHelpers.WidgetConfigurationSave.Valid(customSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
</head>
<body>
<div class="container">
<fieldset>
<label class="label">Query: </label>
<select id="query-path-dropdown" style="margin-top:10px">
<option value="" selected disabled hidden>Please select a query</option>
<option value="Shared Queries/Feedback">Shared Queries/Feedback</option>
<option value="Shared Queries/My Bugs">Shared Queries/My Bugs</option>
<option value="Shared Queries/My Tasks">Shared Queries/My Tasks</option>
</select>
</fieldset>
</div>
</body>
</html>
Passo 4: JavaScript - Implementar recarga no widget
Configuramos a configuração do widget para armazenar o caminho de consulta selecionado pelo usuário.
Agora temos que atualizar o código no widget para usar essa configuração armazenada em vez do hard-coded Shared Queries/Feedback
do exemplo anterior.
Abra o arquivo hello-world3.html
e atualize o nome do widget de HelloWorldWidget2
para HelloWorldWidget3
na linha onde você chama VSS.register
.
Isso permite que a estrutura identifique exclusivamente o widget dentro da extensão.
A função mapeada para HelloWorldWidget3
via VSS.register
atualmente retorna um objeto que satisfaz o IWidget
contrato.
Como nosso widget agora precisa de configuração, essa função precisa ser atualizada para retornar um objeto que satisfaça o IConfigurableWidget
contrato.
Para fazer isso, atualize a instrução return para incluir uma propriedade chamada reload como abaixo. O valor dessa propriedade é uma função que chama o getQueryInfo
método mais uma vez.
Esse método de recarga é chamado pela estrutura toda vez que a entrada do usuário é alterada para mostrar a visualização ao vivo. Isso também é chamado quando a configuração é salva.
return {
load: function (widgetSettings) {
// Set your title
var $title = $('h2.title');
$title.text('Hello World');
return getQueryInfo(widgetSettings);
},
reload: function (widgetSettings) {
return getQueryInfo(widgetSettings);
}
}
O caminho de consulta codificado em 'getQueryInfo' deve ser substituído pelo caminho de consulta configurado, que pode ser extraído do parâmetro 'widgetSettings' que é passado para o método. Adicione o abaixo no início do método 'getQueryInfo' e substitua o querypath codificado por 'settings.queryPath'.
var settings = JSON.parse(widgetSettings.customSettings.data);
if (!settings || !settings.queryPath) {
var $container = $('#query-info-container');
$container.empty();
$container.text("Sorry nothing to show, please configure a query path.");
return WidgetHelpers.WidgetStatusHelper.Success();
}
Neste ponto, seu widget está pronto para renderizar com as configurações configuradas.
Tanto o como as
load
reload
propriedades têm uma função semelhante. Este é o caso da maioria dos widgets simples. Para widgets complexos, haveria certas operações que você gostaria de executar apenas uma vez, não importa quantas vezes a configuração mude. Ou pode haver algumas operações pesadas que não precisam ser executadas mais de uma vez. Tais operações fariam parte da função correspondente à propriedade e não àload
reload
propriedade.
Etapa 5: Atualizações do manifesto de extensão
Abra o vss-extension.json
arquivo para incluir duas novas entradas na matriz na contributions
propriedade. Um para o widget e outro para a HelloWorldWidget3
sua configuração.
Você precisa de mais uma imagem de visualização para o terceiro widget. Nomeie-o preview3.png
e coloque-o img
na pasta.
Atualize a files
matriz na propriedade para incluir os dois novos arquivos HTML que adicionamos neste exemplo.
{
...
"contributions": [
... ,
{
"id": "HelloWorldWidget3",
"type": "ms.vss-dashboards-web.widget",
"targets": [
"ms.vss-dashboards-web.widget-catalog",
"fabrikam.vsts-extensions-myExtensions.HelloWorldWidget.Configuration"
],
"properties": {
"name": "Hello World Widget 3 (with config)",
"description": "My third widget",
"previewImageUrl": "img/preview3.png",
"uri": "hello-world3.html",
"supportedSizes": [
{
"rowSpan": 1,
"columnSpan": 2
}
],
"supportedScopes": ["project_team"]
}
},
{
"id": "HelloWorldWidget.Configuration",
"type": "ms.vss-dashboards-web.widget-configuration",
"targets": [ "ms.vss-dashboards-web.widget-configuration" ],
"properties": {
"name": "HelloWorldWidget Configuration",
"description": "Configures HelloWorldWidget",
"uri": "configuration.html"
}
}
],
"files": [
{
"path": "hello-world.html", "addressable": true
},
{
"path": "hello-world2.html", "addressable": true
},
{
"path": "hello-world3.html", "addressable": true
},
{
"path": "configuration.html", "addressable": true
},
{
"path": "sdk/scripts", "addressable": true
},
{
"path": "img", "addressable": true
}
],
...
}
Observe que a contribuição para a configuração do widget segue um modelo ligeiramente diferente do widget em si. Uma entrada de contribuição para a configuração do widget tem:
- O ID para identificar a sua contribuição. Isso deve ser único dentro de uma extensão.
- O tipo de contribuição. Para todas as configurações de widget, isso deve ser
ms.vss-dashboards-web.widget-configuration
- O conjunto de objetivos para os quais a contribuição está a contribuir. Para todas as configurações de widget, isso tem uma única entrada:
ms.vss-dashboards-web.widget-configuration
. - As propriedades que contêm um conjunto de propriedades que inclui nome, descrição e o URI do arquivo HTML usado para configuração.
Para suportar a configuração, a contribuição do widget também precisa ser alterada. A matriz de destinos para o widget precisa ser atualizada para incluir o ID para a configuração no formato <publisher
>.. que, neste caso, é .<id for the extension
><id for the configuration contribution
> fabrikam.vsts-extensions-myExtensions.HelloWorldWidget.Configuration
Aviso
Se a entrada de contribuição para seu widget configurável não direcionar a configuração usando o editor e o nome da extensão corretos, conforme descrito anteriormente, o botão configurar não aparecerá para o widget.
No final desta parte, o arquivo de manifesto deve conter três widgets e uma configuração. Você pode obter o manifesto completo da amostra aqui.
Etapa 6: Empacotar, publicar e compartilhar
Se você ainda não publicou sua extensão, leia esta seção para empacotar, publicar e compartilhar sua extensão. Se você já publicou a extensão antes deste ponto, pode reempacotá-la e atualizá-la diretamente para o Marketplace.
Etapa 7: Adicionar widget do catálogo
Agora, vá para o painel da sua equipe em https://dev.azure.com/{yourOrganization}/{yourProject}. Se esta página já estiver aberta, atualize-a. Passe o cursor sobre o botão Editar no canto inferior direito e selecione o botão Adicionar. Isso deve abrir o catálogo de widgets onde você encontra o widget que você instalou. Escolha o seu widget e selecione o botão 'Adicionar' para adicioná-lo ao seu painel.
Você verá uma mensagem pedindo para configurar o widget.
Há duas maneiras de configurar widgets. Uma delas é passar o mouse sobre o widget, selecionar as reticências que aparecem no canto superior direito e, em seguida, selecionar Configurar. A outra é selecionar o botão Editar no canto inferior direito do painel e, em seguida, selecionar o botão de configuração que aparece no canto superior direito do widget. Abra a experiência de configuração no lado direito e uma visualização do widget no centro. Vá em frente e escolha uma consulta na lista suspensa. A visualização ao vivo mostra os resultados atualizados. Selecione "Salvar" e seu widget exibirá os resultados atualizados.
Etapa 8: Configurar mais (opcional)
Você pode adicionar quantos elementos de formulário HTML forem necessários na configuration.html
configuração adicional.
Há dois recursos configuráveis que estão disponíveis imediatamente: nome e tamanho do widget.
Por padrão, o nome que você fornece para seu widget no manifesto da extensão é armazenado como o nome do widget para cada instância do widget que é adicionada a um painel.
Você pode permitir que os usuários configurem isso, para que eles possam adicionar qualquer nome que desejarem à instância do widget.
Para permitir essa configuração, adicione isNameConfigurable:true
na seção de propriedades do seu widget no manifesto da extensão.
Se você fornecer mais de uma entrada para seu supportedSizes
widget na matriz no manifesto da extensão, os usuários também poderão configurar o tamanho do widget.
O manifesto de extensão para o terceiro exemplo neste guia seria parecido com o abaixo se ativarmos a configuração de nome e tamanho do widget:
{
...
"contributions": [
... ,
{
"id": "HelloWorldWidget3",
"type": "ms.vss-dashboards-web.widget",
"targets": [
"ms.vss-dashboards-web.widget-catalog", "fabrikam.vsts-extensions-myExtensions.HelloWorldWidget.Configuration"
],
"properties": {
"name": "Hello World Widget 3 (with config)",
"description": "My third widget",
"previewImageUrl": "img/preview3.png",
"uri": "hello-world3.html",
"isNameConfigurable": true,
"supportedSizes": [
{
"rowSpan": 1,
"columnSpan": 2
},
{
"rowSpan": 2,
"columnSpan": 2
}
],
"supportedScopes": ["project_team"]
}
},
...
}
Com a alteração anterior, reempacote e atualize sua extensão. Atualize o painel que tem este widget (Hello World Widget 3 (with config)). Abra o modo de configuração para o seu widget, agora você deve ser capaz de ver a opção para alterar o nome e o tamanho do widget.
Vá em frente e escolha um tamanho diferente na lista suspensa. Você vê a visualização ao vivo ser redimensionada. Salve a alteração e o widget no painel também será redimensionado.
Aviso
Se você remover um tamanho já suportado, o widget não será carregado corretamente. Estamos trabalhando em uma correção para uma versão futura.
Alterar o nome do widget não resulta em nenhuma alteração visível no widget. Isso ocorre porque nossos widgets de exemplo não exibem o nome do widget em nenhum lugar. Vamos modificar o código de exemplo para exibir o nome do widget em vez do texto codificado "Hello World".
Para fazer isso, substitua o texto codificado "Hello World" pela widgetSettings.name
linha onde definimos o h2
texto do elemento.
Isso garante que o nome do widget seja exibido sempre que o widget for carregado na atualização da página.
Como queremos que a visualização ao vivo seja atualizada sempre que a configuração for alterada, devemos adicionar o mesmo código na reload
parte do nosso código também.
A declaração de retorno final é hello-world3.html
a seguinte:
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);
}
}
Reempacote e atualize sua extensão novamente. Atualize o painel que tem esse widget. Quaisquer alterações no nome do widget, no modo de configuração, atualizem o título do widget agora.
Comentários
https://aka.ms/ContentUserFeedback.
Brevemente: Ao longo de 2024, vamos descontinuar progressivamente o GitHub Issues como mecanismo de feedback para conteúdos e substituí-lo por um novo sistema de feedback. Para obter mais informações, veja:Submeter e ver comentários