Adicionar um widget de painel

Azure DevOps Services | 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 várias contribuições. Saiba como criar uma extensão com vários widgets como contribuições.

Este artigo é dividido em três partes, cada uma com base no anterior – começando com um widget simples e terminando com um widget abrangente.

Dica

Confira nossa documentação mais recente sobre o desenvolvimento de extensão usando o SDK da 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 que você precisará:

Conhecimento: Alguns conhecimentos sobre JavaScript, HTML, CSS são necessários para o desenvolvimento de widget.

  • 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 podem ser baixados aqui
  • A versão mais recente do , que pode ser baixada aqui
  • CLI multiplataforma do Azure DevOps (tfx-cli) para empacotar suas extensões.
    • tfx-cli pode ser instalado usando npm, um componente de Node.js executando npm i -g tfx-cli
  • Um diretório base para seu projeto. Esse diretório é conhecido como home ao longo do 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

  1. A primeira parte deste guia mostra como criar um novo widget, que imprime uma mensagem simples de "Olá, Mundo".
  2. A segunda parte se baseia na primeira, adicionando uma chamada a uma API REST do Azure DevOps.
  3. A terceira parte explica como adicionar configuração ao seu widget.

Observação

Se você estiver com pressa e quiser colocar as mãos no código imediatamente, baixe os exemplos aqui. Depois de baixado, vá para a pasta e siga a widgetsEtapa 6 e a Etapa 7 diretamente para publicar a extensão de exemplo que tem os três widgets de exemplo de complexidades variadas.

Comece a usar alguns estilos básicos para widgets que fornecemos pronto para você e algumas diretrizes sobre a estrutura do widget.

Parte 1: Olá, Mundo

Esta parte apresenta um widget que imprime "Olá, Mundo" usando JavaScript.

Overview dashboard with a sample widget

Etapa 1: Obter o SDK do cliente – VSS.SDK.min.js

O script principal do SDK, VSS.SDK.min.js, permite que as extensões da Web se comuniquem com o quadro do host do Azure DevOps. O script faz operações como inicializar, notificar que a extensão é 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 na home/sdk/scripts 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.

Etapa 2: sua página HTML – hello-world.html

Sua página HTML é a cola que mantém o layout unido e inclui referências a CSS e JavaScript. Você pode nomear esse arquivo qualquer coisa, apenas certifique-se de atualizar todas as referências para hello-world com o nome que 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 cadeia de caracteres Olá, Mundo 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>

Embora estejamos usando um arquivo HTML, a maioria dos elementos de cabeçalho HTML que não sejam script e link são ignorados pela estrutura.

Etapa 3: Seu JavaScript

Usamos JavaScript para renderizar o conteúdo no widget. Neste artigo, encapsulamos todo o nosso código JavaScript dentro de um &lt;script&gt; elemento no arquivo HTML. Você pode optar por ter esse código em um arquivo JavaScript separado e encaminhá-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 de êxitos ou falhas do widget. Em nosso caso, abaixo está o código que imprimiria "Olá, Mundo" no widget. Adicione esse 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. explicitNotifyLoaded: true Passamos para que o widget possa notificar explicitamente o host quando terminarmos de carregar. Esse controle nos permite notificar a conclusão da carga depois de garantir que os módulos dependentes sejam carregados. usePlatformStyles: true Passamos para que os estilos principais do Azure DevOps para elementos html (como corpo, div e assim por diante) possam ser usados pelo Widget. Se o widget preferir não usar esses estilos, ele poderá passar usePlatformStyles: false.

VSS.require é usado para carregar as bibliotecas de script VSS necessárias. Uma chamada para esse método carrega automaticamente bibliotecas gerais como JQuery e JQueryUI. Em nosso caso, dependemos da biblioteca WidgetHelpers, que é usada para comunicar o status do widget à estrutura do widget. Portanto, passamos o nome TFS/Dashboards/WidgetHelpers do módulo correspondente e um retorno de chamada para VSS.require. O retorno de chamada é chamado depois que o módulo é carregado. O retorno de chamada tem o restante do código JavaScript necessário para o widget. No final do retorno de chamada, chamamos VSS.notifyLoadSucceeded para notificar a conclusão da carga.

WidgetHelpers.IncludeWidgetStyles inclui uma folha de estilos com alguns css básicos para começar. Lembre-se de encapsular o conteúdo dentro de um elemento HTML com a classe widget para usar esses 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 id que identifica sua contribuição, conforme descrito na Etapa 5. Para widgets, a função que é passada para VSS.register deve retornar um objeto que satisfaça o IWidget contrato, por exemplo, o objeto retornado deve ter uma propriedade de carga cujo valor é outra função que tem a lógica principal para renderizar o widget. Em nosso caso, é para atualizar o texto do h2 elemento para "Olá, Mundo". É essa função que é chamada quando a estrutura do widget cria uma instância do widget. Usamos o WidgetStatusHelper de WidgetHelpers para retornar o WidgetStatus como êxito.

Aviso

Se o nome usado para registrar o widget não corresponder à 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 desejada dentro da pasta, apenas certifique-se de atualizar as referências adequadamente nos arquivos HTML e no vss-extension.json manifesto.

Etapa 4: logotipo da extensão: logo.png

Seu logotipo é exibido no Marketplace e no catálogo de widget depois que um usuário instala sua extensão.

Você precisa de um ícone de catálogo 98 px x 98-px. Escolha uma imagem, nomeie-a logo.pnge coloque-a na img pasta .

Para dar suporte ao TFS 2015 Atualização 3, você precisa de uma imagem adicional que seja 330 px x 160 px. Esta imagem de visualização é mostrada neste catálogo. Escolha uma imagem, nomeie-a preview.pnge coloque-a na img pasta como antes.

Você pode nomear essas imagens como desejar, desde que o manifesto da extensão na próxima etapa seja atualizado com os nomes que você usa.

Etapa 5: manifesto da extensão: vss-extension.json

Crie um arquivo json (vss-extension.jsonpor 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 de manifesto de extensão

Observação

O editor aqui precisa ser alterado para o nome do editor. Para criar um editor agora, visite Pacote/Publicação/Instalação.

Ícones

A estrofe de ícones especifica o caminho para o ícone da extensão em seu manifesto.

Contribuições

Cada entrada de contribuição define as propriedades.

  • A ID para identificar sua contribuição. Essa ID deve ser exclusiva dentro de uma extensão. Essa 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.
  • A matriz de destinos para os quais a contribuição está contribuindo. Para todos os widgets, o destino 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 propriedades a seguir são obrigatórias.
Propriedade Descrição
name Nome do widget a ser exibido no catálogo do widget.
descrição Descrição do widget a ser exibido no catálogo do widget.
catalogIconUrl Caminho relativo do ícone de catálogo que você adicionou na Etapa 4 para exibir no catálogo do widget. A imagem deve ter 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.
previewImageUrl Caminho relativo da imagem de visualização que você adicionou na Etapa 4 para exibir somente no catálogo de widgets do TFS 2015 Atualização 3. A imagem deve ter 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 com suporte pelo widget. Quando um widget dá suporte a 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 obtém 10 px adicionais que representam a medianiz entre widgets. Por exemplo, um widget 3x2 é 160*3+10*2 largo e 160*2+10*1 alto. O tamanho máximo com suporte é 4x4.
supportedScopes No momento, damos suporte apenas a painéis de equipe. O valor deve ser project_team. No futuro, quando oferecermos suporte a outros escopos de painel, haverá mais opções para escolher aqui.

Arquivos

A estrofe de arquivos declara os arquivos que você deseja incluir em seu pacote – sua página HTML, seus scripts, o script do SDK e seu logotipo. Defina addressable como, true a menos que você inclua outros arquivos que não precisam ser endereçáveis por URL.

Observação

Para obter mais informações sobre o arquivo de manifesto de extensão, como suas propriedades e o que elas fazem, confira a referência de manifesto da extensão.

Etapa 6: Empacotar, publicar e compartilhar

Depois de escrever sua extensão, a próxima etapa para que ela seja colocada 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 CLI (interface de linha de comando) multiplataforma para empacotar sua extensão.

Obter a ferramenta de empacotamento

Você pode instalar ou atualizar a CLI multiplataforma do Azure DevOps (tfx-cli) usando npm, um componente de Node.js, da linha de comando.

npm i -g tfx-cli

Empacotar sua extensão

Empacotar sua extensão em um arquivo .vsix é fácil quando você tem o tfx-cli. Acesse o diretório base da extensão e execute o comando a seguir.

tfx extension create --manifest-globs vss-extension.json

Observação

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 --rev-version linha de comando. Isso incrementa o número de versão do patch da extensão e salva a nova versão no 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 publicador existente, criará um.

  1. Entrar no Portal de Publicação do Visual Studio Marketplace
  2. Se você ainda não for membro de um publicador existente, será solicitado que você crie 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 do publisher atributo no arquivo de manifesto das extensões.
    • Especifique um nome de exibição para o editor, por exemplo: My Team
  3. Examine o Contrato de Editor do Marketplace e selecione Criar

Agora 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 vss-extension.json arquivo de manifesto nos exemplos para substituir a ID fabrikam do editor fictício pela ID do editor.

Publicar e compartilhar a extensão

Depois de criar um editor, agora você pode carregar sua extensão no Marketplace.

  1. Localize o botão Carregar nova extensão , navegue até o arquivo .vsix empacotado e selecione carregar.

Você também pode carregar sua extensão por meio 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

  1. Acesse seu projeto no Azure DevOps, http://dev.azure.com/{yourOrganization}/{yourProject}

  2. Selecione Visão geral e, em seguida, selecione Painéis.

  3. Escolha Adicionar um widget.

  4. Realce o widget e selecione Adicionar.

    O widget aparece no 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 "Olá, Mundo".

Overview dashboard with a sample widget using the REST API for WorkItemTracking.

Etapa 1: HTML

Copie o arquivo hello-world.html do exemplo anterior e renomeie a cópia para hello-world2.html. Sua pasta agora tem a seguinte aparência:

|--- 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 do '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 recursos do Azure DevOps

Para habilitar o acesso aos recursos do Azure DevOps, os escopos precisam ser especificados no manifesto da extensão. Adicionamos o vso.work escopo 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 da extensão.

{
    ...,
    "scopes":[
        "vso.work"
    ]
}

Aviso

No momento, não há suporte para a adição ou alteração de escopos após a publicação de uma extensão. Se você já carregou sua extensão, remova-a do Marketplace. Acesse o Visual Studio Marketplace Publishing Portal, 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 à 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 por conta própria. Esses métodos mapeiam as respostas da API para objetos que podem ser consumidos pelo código.

Nesta etapa, atualizamos a VSS.require chamada para carregar TFS/WorkItemTracking/RestClient, que fornece o cliente REST WorkItemTracking. Podemos usar esse cliente REST para obter informações sobre uma consulta chamada Feedback na pasta Shared Queries.

Dentro da função que passamos para VSS.register, criamos uma variável para manter a 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. Esse método, em seguida, é chamado 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 na Shared Queries pasta , substitua Shared 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 de 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 no texto "Olá, Mundo". 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);

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: manifesto de extensão Atualizações

Nesta etapa, atualizamos o manifesto da extensão para incluir uma entrada para nosso segundo widget. Adicione uma nova contribuição à matriz na contributions propriedade e adicione o novo arquivo hello-world2.html à matriz na propriedade files. Você precisa de outra imagem de visualização para o segundo widget. Nomeie-o preview2.png e coloque-o na img 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

Empacotar, publicar e compartilhar sua extensão. Se você já publicou a extensão, poderá reempacotá-la e atualizá-la diretamente para o Marketplace.

Etapa 7: Adicionar widget do catálogo

Agora, vá para o painel da equipe em https:\//dev.azure.com/{yourOrganization}/{yourProject}. Se essa página já estiver aberta, atualize-a. Passe o mouse sobre o botão Editar na parte inferior direita e selecione o botão Adicionar. O catálogo de widgets é aberto onde você encontra o widget instalado. Escolha o widget e selecione o botão 'Adicionar' para adicioná-lo ao 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 embutida em código. Nesta parte, adicionamos a capacidade de configurar a consulta a ser usada em vez da codificada. Quando estiver no modo de configuração, o usuário verá uma visualização dinâmica do widget com base em suas alterações. Essas alterações são salvas no widget no painel quando o usuário seleciona Salvar.

Overview dashboard live preview of the widget based on changes.

Etapa 1: HTML

As implementações de widgets e configurações de widget são muito parecidas. Ambos são implementados na estrutura de extensão como contribuições. Ambos usam o mesmo arquivo do 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 seguinte exemplo:

|--- 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 'configuration.html'. Basicamente, adicionamos 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 o JavaScript para renderizar conteúdo na configuração do widget, assim como fizemos para o widget na Etapa 3 da Parte 1 neste guia. Esse código JavaScript renderiza conteúdo, inicializa o SDK do VSS, mapeia o código para a configuração do widget para o nome da configuração e passa as definições de configuração para a estrutura. Em nosso caso, abaixo está o código que carrega a configuração do widget. Abra o arquivo configuration.html e o elemento abaixo <script> para o <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.requiree VSS.register desempenham a mesma função que desempenharam para o widget, conforme descrito na Parte 1. A única diferença é que, para configurações de widget, a função passada para 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. Essa função tem o conjunto de etapas para renderizar a configuração do widget. Em nosso caso, é para atualizar o valor selecionado do elemento suspenso com as configurações existentes, se houver. Essa função é chamada quando a estrutura cria uma instância da sua 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 na cadeia de caracteres. Ele é acessível para o widget por meio da propriedade customSettings do WidgetSettings objeto . O widget precisa desserializar isso, que é abordado na Etapa 4.

Etapa 3: JavaScript – Habilitar a Visualização Dinâmica

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 a ser usado para atualizar a visualização. Para notificar a estrutura, o notify método no widgetConfigurationContext precisa ser chamado. Ele usa dois parâmetros, o nome do evento, que nesse caso é WidgetHelpers.WidgetEvent.ConfigurationChange, e um EventArgs objeto para o evento, criados com base no customSettings com a 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 habilitado.

No final, seu configuration.html tem 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("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>

Etapa 4: JavaScript – Implementar o recarregamento no widget

Definimos a configuração do widget para armazenar o caminho de consulta selecionado pelo usuário. Agora, precisamos atualizar o código no widget para usar essa configuração armazenada em vez do embutido em código Shared Queries/Feedback do exemplo anterior.

Abra o arquivo hello-world3.html e atualize o nome do widget de HelloWorldWidget2 para HelloWorldWidget3 na linha em que você chama VSS.register. Isso permite que a estrutura identifique exclusivamente o widget dentro da extensão.

A função mapeada para HelloWorldWidget3 por meio VSS.register do 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 atenda ao IConfigurableWidget contrato. Para fazer isso, atualize a instrução return para incluir uma propriedade chamada recarregar conforme mostrado abaixo. O valor dessa propriedade é uma função que chama o getQueryInfo método mais uma vez. Esse método de recarregamento é chamado pela estrutura sempre 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 embutido em código 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 caminho de consulta embutido em código 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, o widget está pronto para ser renderizado com as configurações definidas.

load As propriedades e reload têm uma função semelhante. Esse é o caso da maioria dos widgets simples. Para widgets complexos, haveria certas operações que você gostaria de executar apenas uma vez, independentemente de quantas vezes a configuração for alterada. Ou pode haver algumas operações pesadas que não precisam ser executadas mais de uma vez. Essas operações seriam parte da função correspondente à load propriedade e não à reload propriedade .

Etapa 5: manifesto de extensão Atualizações

Abra o vss-extension.json arquivo para incluir duas novas entradas na matriz na contributions propriedade . Um para o HelloWorldWidget3 widget e outro para sua configuração. Você precisa de mais uma imagem de visualização para o terceiro widget. Nomeie-o preview3.png e coloque-o na img pasta . Atualize a matriz na files 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:
  • A ID para identificar sua contribuição. Isso deve ser exclusivo 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
  • A matriz de destinos para os quais a contribuição está contribuindo. 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 o nome, a descrição e o URI do arquivo HTML usado para configuração.

Para dar suporte à configuração, a contribuição do widget também precisa ser alterada. A matriz de destinos para o widget precisa ser atualizada para incluir a ID da configuração no formato <>publisher.<id for the extension>.id for the configuration contribution<> que, nesse caso, é .fabrikam.vsts-extensions-myExtensions.HelloWorldWidget.Configuration

Aviso

Se a entrada de contribuição do 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 do exemplo 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, você pode reempacotá-la e atualizá-la diretamente para o Marketplace.

Etapa 7: Adicionar widget do catálogo

Agora, vá para o painel da equipe em https://dev.azure.com/{yourOrganization}/{yourProject}. Se esta página já estiver aberta, atualize-a. Passe o mouse sobre o botão Editar na parte inferior direita e selecione o botão Adicionar. Isso deve abrir o catálogo de widgets em que você encontra o widget instalado. Escolha o widget e selecione o botão 'Adicionar' para adicioná-lo ao painel.

Você verá uma mensagem solicitando que você configure o widget.

Overview dashboard with a sample widget from the catalog.

Há duas maneiras de configurar widgets. Uma delas é focalizar o widget, selecionar as reticências exibidas no canto superior direito e, em seguida, selecionar Configurar. A outra é selecionar o botão Editar na parte inferior direita do painel e, em seguida, selecionar o botão configurar que aparece no canto superior direito do widget. Abre 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 o widget exibe os resultados atualizados.

Etapa 8: Configurar mais (opcional)

Você pode adicionar quantos elementos de formulário HTML forem necessários no configuration.html para configuração adicional. Há dois recursos configuráveis que estão disponíveis pronto para uso: nome do widget e tamanho do widget.

Por padrão, o nome que você fornece para o widget no manifesto da extensão é armazenado como o nome do widget para cada instância do widget que é adicionado 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 propriedades do widget no manifesto da extensão.

Se você fornecer mais de uma entrada para o 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 será semelhante ao mostrado abaixo se habilitarmos 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 esse widget (Olá, Mundo Widget 3 (com configuração)). Abra o modo de configuração do widget. Agora você poderá ver a opção de alterar o nome e o tamanho do widget.

Widget where name and size can be configured

Vá em frente e escolha um tamanho diferente da lista suspensa. Você verá 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á compatível, 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 embutido em código "Olá, Mundo".

Para fazer isso, substitua o texto embutido em código "Olá, Mundo" widgetSettings.name por na linha em que definimos o texto do h2 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, também devemos adicionar o mesmo código na reload parte do nosso código. A instrução de retorno final em 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);
    }
}

Reempacotar e atualizar sua extensão novamente. Atualize o painel que tem esse widget. Todas as alterações no nome do widget, no modo de configuração, atualizam o título do widget agora.