添加仪表板小组件

Azure DevOps Services | Azure DevOps Server 2022 - Azure DevOps Server 2019

仪表板上的小组件在扩展框架中作为贡献实现。 单个扩展可以有多个贡献。 了解如何创建包含多个小组件作为贡献的扩展。

本文分为三个部分,每个部分都基于上一个部分-从一个简单的小组件开始,以一个全面的小组件结束。

提示

查看有关使用 Azure DevOps 扩展 SDK 进行扩展开发的最新文档。

先决条件

  • 知识: 小组件开发需要一些 JavaScript、HTML、CSS 知识。
  • Azure DevOps 中的组织
  • 文本编辑器。 对于许多教程,我们使用 Visual Studio Code
  • 最新版本 节点
  • 适用于 Azure DevOps 的跨平台 CLI (tfx-cli) 打包扩展。
    • 可以使用 安装 npmtfx-cli,通过运行 Node.js 的组件npm i -g tfx-cli
  • 项目的主目录。 此目录在本教程中称为 home

扩展文件结构:

|--- 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

本教程的内容

  1. 第 1 部分:演示如何创建新小组件,该小组件打印简单的“Hello World”消息。
  2. 第 2 部分:通过添加对 Azure DevOps REST API 的调用来构建第一部分。
  3. 第 3 部分:介绍如何向小组件添加配置。

注意

如果匆忙地想立即掌握代码,可以下载 示例。 下载后,转到 widgets 文件夹,然后按照 步骤 6步骤 7 直接发布示例扩展,该扩展具有三个不同复杂性的示例小组件。

我们针对小组件提供现装的一些 基本样式和有关小组件 结构的一些指导入门。

第 1 部分:Hello World

第 1 部分演示了使用 JavaScript 打印“Hello World”的小组件。

包含示例小组件的“概述”仪表板的屏幕截图。

步骤 1:获取客户端 SDK - VSS.SDK.min.js

核心 SDK 脚本 VSS.SDK.min.js使 Web 扩展能够与主机 Azure DevOps 框架通信。 该脚本执行初始化、通知扩展加载或获取当前页上下文等操作。 获取客户端 SDK VSS.SDK.min.js 文件并将其添加到 Web 应用。 将其放在 home/sdk/scripts 文件夹中。

若要检索 SDK,请使用“npm install”命令:

npm install vss-web-extension-sdk

有关详细信息,请参阅 客户端 SDK GitHub 页

步骤 2:设置 HTML 页面 - hello-world.html

HTML 页面是将布局组合在一起的粘附,包含对 CSS 和 JavaScript 的引用。 你可以将此文件命名为任何内容。 使用你使用的名称更新所有 hello-world 引用。

小组件基于 HTML,托管在 iframe 中。 在 . 中添加 hello-world.html以下 HTML。 我们将添加对 VSS.SDK.min.js 文件的必需引用,并包含一个 h2 元素,该元素在后续步骤中使用字符串 Hello World 进行更新。

<!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>

尽管我们使用的是 HTML 文件,但框架忽略除脚本和链接以外的大多数 HTML 头元素。

步骤 3:更新 JavaScript

我们使用 JavaScript 在小组件中呈现内容。 在本文中,我们将所有 JavaScript 代码包装在 HTML 文件中的 元素 &lt;script&gt; 内。 可以选择将此代码包含在单独的 JavaScript 文件中,并在 HTML 文件中引用它。 代码呈现内容。 此 JavaScript 代码还会初始化 VSS SDK,将小组件的代码映射到小组件名称,并通知扩展框架小组件成功或失败。 在本例中,以下代码在小组件中打印“Hello World”。 将此 script 元素 head 添加到 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 初始化承载小组件的 iframe 与主机帧之间的握手。
  • 我们传递 explicitNotifyLoaded: true ,以便小组件在加载完成后显式通知主机。 此控件允许我们在确保加载依赖模块后通知加载完成。 我们传递 usePlatformStyles: true 后,小组件可以将 Azure DevOps 核心样式用于 HTML 元素(如正文、div 等)。 如果小组件希望不使用这些样式,则可以传入 usePlatformStyles: false
  • VSS.require 用于加载所需的 VSS 脚本库。 对此方法的调用会自动加载常规库,例如 JQueryJQueryUI。 在本例中,我们依赖于 WidgetHelpers 库,该库用于将小组件状态传达给小组件框架。 因此,我们将相应的模块名称和 TFS/Dashboards/WidgetHelpers 回调传递给 VSS.require。 加载模块后,将调用回调。 回调包含小组件所需的其余 JavaScript 代码。 在回调结束时,我们调用 VSS.notifyLoadSucceeded 以通知加载完成。
  • WidgetHelpers.IncludeWidgetStyles 包括一个样式表,其中包含一些 基本的 css ,可帮助你入门。 若要使用这些样式,请将内容包装在包含类 widget的 HTML 元素中。
  • VSS.register 用于在 JavaScript 中映射函数,该函数在扩展的不同贡献之间唯一标识小组件。 该名称应与标识你的贡献的 匹配 id ,如 步骤 5 中所述。 对于小组件,传递给 VSS.register 的函数应返回一个满足 IWidget 协定的对象,例如,返回的对象应具有一个 load 属性,其值为另一个具有核心逻辑的函数,用于呈现小组件。 在本例中,将元素的文本 h2 更新为“Hello World”。这是在小组件框架实例化小组件时调用的此函数。 我们使用 WidgetStatusHelper 来自 WidgetHelpers 的 将 返回 WidgetStatus 为成功。

警告

如果用于注册小组件的名称与清单中贡献的 ID 不匹配,则小组件会意外运行。

  • vss-extension.json应始终位于文件夹的根目录(本指南中)。 HelloWorld 对于所有其他文件,你可以将它们放在文件夹中所需的任何结构中,只需确保适当地更新 HTML 文件和清单中的 vss-extension.json 引用即可。

步骤 4:更新扩展徽标: logo.png

用户安装扩展后,徽标将显示在市场和小组件目录中。

需要 98 px x 98-px 目录图标。 选择一个图像,将其 logo.png命名为 ,并将其放在 文件夹中 img

只要下一步中的扩展清单使用你使用的名称更新,就可以根据需要命名这些映像。

步骤 5:创建扩展清单: vss-extension.json

每个 扩展都必须有一个扩展清单文件。

  • 读取扩展清单引用
  • 详细了解扩展点中的贡献点。
  • (vss-extension.json创建 json 文件,例如,在 home 目录中创建包含以下内容的) :
{
    "manifestVersion": 1,
    "id": "azure-devops-extensions-myExtensions",
    "version": "1.0.0",
    "name": "My First Set of Widgets",
    "description": "Samples containing different widgets extending dashboards",
    "publisher": "fabrikam",
    "categories": ["Azure Boards"],
    "targets": [
        {
            "id": "Microsoft.VisualStudio.Services"
        }
    ],
    "icons": {
        "default": "img/logo.png"
    },
    "contributions": [
        {
            "id": "HelloWorldWidget",
            "type": "ms.vss-dashboards-web.widget",
            "targets": [
                "ms.vss-dashboards-web.widget-catalog"
            ],
            "properties": {
                "name": "Hello World Widget",
                "description": "My first widget",
                "catalogIconUrl": "img/CatalogIcon.png",
                "previewImageUrl": "img/preview.png",
                "uri": "hello-world.html",
                "supportedSizes": [
                    {
                        "rowSpan": 1,
                        "columnSpan": 2
                    }
                ],
                "supportedScopes": ["project_team"]
            }
        }
    ],
    "files": [
        {
            "path": "hello-world.html",
            "addressable": true
        },
        {
            "path": "sdk/scripts",
            "addressable": true
        },
        {
            "path": "img",
            "addressable": true
        }
    ]
}

有关所需属性的详细信息,请参阅 扩展清单参考

注意

发布者更改为发布者 名称。 若要创建发布者,请参阅 包/发布/安装

图标

图标节指定清单中扩展图标的路径。

参与

每个贡献项都定义 属性

  • 用于标识贡献的 ID 。 此 ID 在扩展中应是唯一的。 此 ID 应与 步骤 3 中用于注册小组件的名称匹配。
  • 贡献 的类型 。 对于所有小组件,类型应为 ms.vss-dashboards-web.widget
  • 参与参与 的目标 数组。 对于所有小组件,目标应为 [ms.vss-dashboards-web.widget-catalog]
  • 属性是包含贡献类型的属性的对象。 对于小组件,以下属性是必需的。
属性 说明
name 要显示在小组件目录中的小组件的名称。
description 要显示在小组件目录中的小组件的说明。
catalogIconUrl 步骤 4 中添加的要在小组件目录中显示的目录图标的相对路径。 图像应为 98 像素 x 98 像素。 如果使用了不同的文件夹结构或其他文件名,请在此处指定相应的相对路径。
previewImageUrl 在步骤 4 中添加的预览图像的相对路径,以在小组件目录中显示。 图像应为 330 像素 x 160 像素。 如果使用了不同的文件夹结构或其他文件名,请在此处指定相应的相对路径。
uri 步骤 1 中添加的 HTML 文件的相对路径。 如果使用了不同的文件夹结构或其他文件名,请在此处指定相应的相对路径。
supportedSizes 小组件支持的大小数组。 当小组件支持多个大小时,数组中的第一个大小是小组件的默认大小。 widget size为仪表板网格中小组件占用的行和列指定 。 一行/列对应于 160 px。 大于 1x1 的任何维度都会获得一个额外的 10 像素,表示小组件之间的装订线。 例如,3x2 小组件宽 160*3+10*2160*2+10*1 高。 支持的最大大小为 4x4
supportedScopes 目前,仅支持团队仪表板。 该值必须为 project_team。 将来的更新可能包括更多仪表板范围选项。

文件

文件节指出要包含在包中的文件 - HTML 页面、脚本、SDK 脚本和徽标。 true将 设置为 addressable ,除非包含不需要 URL 寻址的其他文件。

注意

有关 扩展清单文件的详细信息(例如其属性及其用途),请查看 扩展清单参考

步骤 6:打包、发布和共享

编写扩展后,进入市场的下一步是将所有文件打包在一起。 所有扩展都打包为 VSIX 2.0 兼容的 .vsix 文件 - Microsoft 提供了跨平台命令行接口 (CLI) 来打包扩展。

获取打包工具

可以使用 azure DevOps (tfx-cli) npmNode.js的组件)从命令行安装或更新跨平台 CLI。

npm i -g tfx-cli

打包扩展

使用 tfx-cli 后,将扩展名打包到 .vsix 文件中是毫不费力的。 转到扩展的主目录并运行以下命令。

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

注意

每次更新时,都必须递增扩展/集成版本。
更新现有扩展时,更新清单中的版本或传递 --rev-version 命令行开关。 这会递增扩展的 修补程序 版本号,并将新版本保存到清单。

在 .vsix 文件中打包扩展后,即可将扩展发布到市场。

为扩展创建发布者

所有扩展(包括 Microsoft 的扩展)都标识为由发布者提供。 如果还没有现有发布者的成员,请创建一个。

  1. 登录到 Visual Studio 市场发布门户
  2. 如果还没有现有发布者的成员,则必须创建发布者。 如果已有发布者,请滚动到相关网站下的“发布扩展”并选择“发布扩展”。
    • 指定发布者的标识符,例如: mycompany-myteam
      • 标识符用作扩展清单文件中属性 publisher 的值。
    • 指定发布者的显示名称,例如: My Team
  3. 查看市场发布者协议,然后选择“创建”。

现在,你的发布者已定义。 在将来的版本中,可以授予查看和管理发布者扩展的权限。

在通用发布者下发布扩展简化了团队和组织的过程,从而提供了更安全的方法。 此方法无需在多个用户之间分配一组凭据,从而提高安全性和

更新示例中的 vss-extension.json 清单文件,将虚拟发布者 ID 替换为发布者 ID fabrikam

发布和共享扩展

现在,可以将扩展上传到市场。

选择“ 上传新扩展名”,转到打包的 .vsix 文件,然后选择“ 上传”。

还可以使用 tfx extension publish 命令(而不是一个步骤打包和发布扩展) tfx extension create 通过命令行上传扩展。 发布后,可以选择使用 --share-with 与一个或多个帐户共享扩展。 还需要个人访问令牌。

tfx extension publish --manifest-globs your-manifest.json --share-with yourOrganization

步骤 7:从目录中添加小组件

  1. 登录到项目。 http://dev.azure.com/{Your_Organization}/{Your_Project}

  2. 选择“概述>仪表板”。

  3. 选择“Add a widget”。

  4. 突出显示小组件,然后选择“ 添加”。

    小组件将显示在仪表板上。

第 2 部分:使用 Azure DevOps REST API Hello World

小组件可以调用 Azure DevOps 中的任何 REST API 来与 Azure DevOps 资源交互。 在以下示例中,我们使用 REST API for WorkItemTracking 提取有关现有查询的信息,并在小组件中的“Hello World”文本下显示一些查询信息。

概述仪表板的屏幕截图,其中包含使用 REST API for WorkItemTracking 的示例小组件。

步骤 1:添加 HTML 文件

复制上一示例中的文件 hello-world.html ,并将副本重命名为 hello-world2.html。 文件夹现在如以下示例所示:

|--- README.md |--- node_modules
|--- SDK
|---脚本 |--- VSS。SDK.min.js |--- img |--- logo.png |--- 脚本
|--- 你好-world.html // html 页面,用于小组件 |--- 你好-world2.html // 重命名你好-world.html |--- vss-extension.json // 扩展的清单副本

若要保存查询信息,请在 <a0div/> 下添加新元素。 在调用 VSS.register的行中,将 小组件的名称从 HelloWorldWidget 更新为 HelloWorldWidget2 。 此操作允许框架唯一标识扩展中的小组件。

<!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>

步骤 2:访问 Azure DevOps 资源

若要启用对 Azure DevOps 资源的访问,需要在扩展清单中指定 范围 。 我们将范围 vso.work 添加到清单。
此范围指示小组件需要对查询和工作项进行只读访问。 在此处查看所有可用范围。 在扩展清单末尾添加以下代码。

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

若要包含其他属性,应显式列出它们,例如:

{
    "name": "example-widget",
    "publisher": "example-publisher",
    "version": "1.0.0",
    "scopes": [
        "vso.work"
    ]
}

警告

目前不支持在发布扩展后添加或更改范围。 如果已上传扩展,请将其从市场中删除。 转到 Visual Studio Marketplace 发布门户,右键单击扩展并选择“ 删除”。

步骤 3:进行 REST API 调用

可通过 SDK 访问许多客户端库,以在 Azure DevOps 中进行 REST API 调用。 这些库称为 REST 客户端,是所有可用服务器端终结点的 Ajax 调用的 JavaScript 包装器。 可以使用这些客户端提供的方法,而不是自行编写 Ajax 调用。 这些方法将 API 响应映射到代码可以使用的对象。

在此步骤中 VSS.require ,我们将更新对 load AzureDevOps/WorkItemTracking/RestClient的调用,该调用提供 WorkItemTracking REST 客户端。 可以使用此 REST 客户端获取有关文件夹 Shared Queries下调用Feedback的查询的信息。

在传递给 VSS.register的函数中,创建一个用于保存当前项目 ID 的变量。 我们需要此变量来提取查询。 我们还创建一个新方法 getQueryInfo 以使用 REST 客户端。 然后从 load 方法调用此方法。

方法 getClient 提供所需的 REST 客户端的实例。 方法 getQuery 返回在 promise 中包装的查询。 更新 VSS.require 的 如下所示:

VSS.require(["AzureDevOps/Dashboards/WidgetHelpers", "AzureDevOps/WorkItemTracking/RestClient"], 
    function (WidgetHelpers, TFS_Wit_WebApi) {
        WidgetHelpers.IncludeWidgetStyles();
        VSS.register("HelloWorldWidget2", function () { 
            var projectId = VSS.getWebContext().project.id;

            var getQueryInfo = function (widgetSettings) {
                // Get a WIT client to make REST calls to Azure DevOps Services
                return TFS_Wit_WebApi.getClient().getQuery(projectId, "Shared Queries/Feedback")
                    .then(function (query) {
                        // Do something with the query

                        return WidgetHelpers.WidgetStatusHelper.Success();
                    }, function (error) {                            
                        return WidgetHelpers.WidgetStatusHelper.Failure(error.message);
                    });
            }

            return {
                load: function (widgetSettings) {
                    // Set your title
                    var $title = $('h2.title');
                    $title.text('Hello World');

                    return getQueryInfo(widgetSettings);
                }
            }
        });
        VSS.notifyLoadSucceeded();
    });

请注意,使用 中的 WidgetStatusHelperFailure 方法。 它允许你向小组件框架指示发生了错误,并利用提供给所有小组件的标准错误体验。

Feedback如果没有文件夹下的Shared Queries查询,请将代码中的查询替换为Shared Queries\Feedback项目中存在的查询的路径。

步骤 4:显示响应

最后一步是在小组件内呈现查询信息。 函数 getQuery 在 promise 中返回类型的 Contracts.QueryHierarchyItem 对象。 在此示例中,我们在“Hello World”文本下显示查询 ID、查询名称和查询创建者的名称。 使用下面的代码替换 // Do something with the query 注释:

// Create a list with query details                                
var $list = $('<ul>');                                
$list.append($('<li>').text("Query Id: " + query.id));
$list.append($('<li>').text("Query Name: " + query.name));
$list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName : "<unknown>")));

// Append the list to the query-info-container
var $container = $('#query-info-container');
$container.empty();
$container.append($list);

最终版本 hello-world2.html 如以下示例所示:

<!DOCTYPE html>
<html>
<head>    
    <script src="sdk/scripts/VSS.SDK.min.js"></script>
    <script type="text/javascript">
        VSS.init({
            explicitNotifyLoaded: true,
            usePlatformStyles: true
        });

        VSS.require(["AzureDevOps/Dashboards/WidgetHelpers", "AzureDevOps/WorkItemTracking/RestClient"], 
            function (WidgetHelpers, TFS_Wit_WebApi) {
                WidgetHelpers.IncludeWidgetStyles();
                VSS.register("HelloWorldWidget2", function () {                
                    var projectId = VSS.getWebContext().project.id;

                    var getQueryInfo = function (widgetSettings) {
                        // Get a WIT client to make REST calls to Azure DevOps Services
                        return TFS_Wit_WebApi.getClient().getQuery(projectId, "Shared Queries/Feedback")
                            .then(function (query) {
                                // Create a list with query details                                
                                var $list = $('<ul>');
                                $list.append($('<li>').text("Query ID: " + query.id));
                                $list.append($('<li>').text("Query Name: " + query.name));
                                $list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName : "<unknown>")));

                                // Append the list to the query-info-container
                                var $container = $('#query-info-container');
                                $container.empty();
                                $container.append($list);

                                // Use the widget helper and return success as Widget Status
                                return WidgetHelpers.WidgetStatusHelper.Success();
                            }, function (error) {
                                // Use the widget helper and return failure as Widget Status
                                return WidgetHelpers.WidgetStatusHelper.Failure(error.message);
                            });
                    }

                    return {
                        load: function (widgetSettings) {
                            // Set your title
                            var $title = $('h2.title');
                            $title.text('Hello World');

                            return getQueryInfo(widgetSettings);
                        }
                    }
                });
            VSS.notifyLoadSucceeded();
        });       
    </script>

</head>
<body>
    <div class="widget">
        <h2 class="title"></h2>
        <div id="query-info-container"></div>
    </div>
</body>
</html>

步骤 5:更新扩展清单

在此步骤中,我们将更新扩展清单,以包含第二个小组件的条目。 向 属性中的 contributions 数组添加新贡献,并将新文件 hello-world2.html 添加到 files 属性中的数组。 第二个小组件需要另一个预览图像。 preview2.png将此名称命名为 ,并将其放在 img 文件夹中。

{
    ...,
    "contributions": [
        ...,
        {
            "id": "HelloWorldWidget2",
            "type": "ms.vss-dashboards-web.widget",
            "targets": [
                "ms.vss-dashboards-web.widget-catalog"
            ],
            "properties": {
                "name": "Hello World Widget 2 (with API)",
                "description": "My second widget",
                "previewImageUrl": "img/preview2.png",
                "uri": "hello-world2.html",
                "supportedSizes": [
                    {
                        "rowSpan": 1,
                        "columnSpan": 2
                    }
                ],
                "supportedScopes": ["project_team"]
            }
        }
    ],
    "files": [
        {
            "path": "hello-world.html",
            "addressable": true
        },
        {
            "path": "hello-world2.html",
            "addressable": true
        },
        {
            "path": "sdk/scripts",
            "addressable": true
        },
        {
            "path": "img",
            "addressable": true
        }
    ],
    "scopes": [
        "vso.work"
    ]
}

步骤 6:打包、发布和共享

打包、发布和共享扩展。 如果已发布扩展,可以重新打包扩展并将其直接更新到市场。

步骤 7:从目录中添加小组件

现在,转到团队仪表板。https:\//dev.azure.com/{Your_Organization}/{Your_Project} 如果此页面已打开,请刷新它。 将鼠标悬停在“编辑”,然后选择“添加”。 此时会打开小组件目录,可在其中找到已安装的小组件。 若要将其添加到仪表板,请选择小组件,然后选择“ 添加”。

第 3 部分:配置 Hello World

本指南 的第 2 部分 介绍了如何创建显示硬编码查询的查询信息的小组件。 在本部分中,我们添加了将查询配置为要使用的功能,而不是硬编码查询。 在配置模式下,用户可以根据更改查看小组件的实时预览。 当用户选择“ 保存”时,这些更改将保存到仪表板上的小组件。

基于更改的“概述”仪表板实时预览小组件的屏幕截图。

步骤 1:添加 HTML 文件

小组件和小组件配置的实现非常相似。 两者在扩展框架中作为贡献实现。 两者都使用相同的 SDK 文件 VSS.SDK.min.js。 两者都基于 HTML、JavaScript 和 CSS。

复制上一示例中的文件 html-world2.html ,并将副本重命名为 hello-world3.html。 添加另一个名为 的 configuration.htmlHTML 文件。 文件夹现在如以下示例所示:

|--- 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

在 . 中添加 configuration.html以下 HTML。 我们基本上添加对 文件的必需引用 VSS.SDK.min.js ,以及 select 下拉列表的元素,以从预设列表中选择查询。

    <!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>

步骤 2:配置 JavaScript

使用 JavaScript 在小组件配置中呈现内容,就像本指南第 1 部分 的步骤 3 中针对小组件所做的那样。 此 JavaScript 代码呈现内容、初始化 VSS SDK、将小组件配置的代码映射到配置名称,并将配置设置传递给框架。 在本例中,以下代码加载小组件配置。 打开该文件configuration.html,并将以下<script>元素打开到 .<head>

<script type="text/javascript">
    VSS.init({                        
        explicitNotifyLoaded: true,
        usePlatformStyles: true
    });

    VSS.require(["AzureDevOps/Dashboards/WidgetHelpers"], function (WidgetHelpers) {
        VSS.register("HelloWorldWidget.Configuration", function () {   
            var $queryDropdown = $("#query-path-dropdown"); 

            return {
                load: function (widgetSettings, widgetConfigurationContext) {
                    var settings = JSON.parse(widgetSettings.customSettings.data);
                    if (settings && settings.queryPath) {
                         $queryDropdown.val(settings.queryPath);
                     }

                    return WidgetHelpers.WidgetStatusHelper.Success();
                },
                onSave: function() {
                    var customSettings = {
                        data: JSON.stringify({
                                queryPath: $queryDropdown.val()
                            })
                    };
                    return WidgetHelpers.WidgetConfigurationSave.Valid(customSettings); 
                }
            }
        });
        VSS.notifyLoadSucceeded();
    });
</script>
  • VSS.initVSS.requireVSS.register扮演他们为小组件扮演的角色,如第 1 部分中所述。 唯一的区别是,对于小组件配置,传递给 VSS.register 的函数应返回一个满足 IWidgetConfiguration 协定的对象。
  • 协定 loadIWidgetConfiguration 属性应具有函数作为其值。 此函数包含一组用于呈现小组件配置的步骤。 在本例中,它使用现有设置更新下拉列表元素的选定值(如果有)。 当框架实例化你的 widget configuration
  • 协定 onSaveIWidgetConfiguration 属性应具有函数作为其值。 当用户在配置窗格中选择 “保存 ”时,框架将调用此函数。 如果用户输入已准备好保存,则将其序列化为字符串,形成 custom settings 对象并使用 WidgetConfigurationSave.Valid() 保存用户输入。

在本指南中,我们使用 JSON 将用户输入序列化为字符串。 可以选择任何其他方法将用户输入序列化为字符串。 可通过对象的 customSettings 属性 WidgetSettings 访问小组件。 小组件必须反序列化,步骤 4介绍了该小组件。

步骤 3:JavaScript - 启用实时预览

为了在用户从下拉列表中选择查询时启用实时预览更新,我们将更改事件处理程序附加到按钮。 此处理程序通知框架配置已更改。 它还传递 customSettings 要用于更新预览的 。 若要通知框架, notify 需要调用 上的 widgetConfigurationContext 方法。 它采用两个EventArgs参数:事件的名称(在本例中为 WidgetHelpers.WidgetEvent.ConfigurationChange),以及事件的对象(借助 customSettingsWidgetEvent.Args helper 方法创建)。

在分配给属性的 load 函数中添加以下代码。

 $queryDropdown.on("change", function () {
     var customSettings = {
        data: JSON.stringify({
                queryPath: $queryDropdown.val()
            })
     };
     var eventName = WidgetHelpers.WidgetEvent.ConfigurationChange;
     var eventArgs = WidgetHelpers.WidgetEvent.Args(customSettings);
     widgetConfigurationContext.notify(eventName, eventArgs);
 });

已修订:确保框架至少收到配置更改通知一次,以启用 “保存 ”按钮。

最后,如 configuration.html 以下示例所示:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>                          
        <script src="sdk/scripts/VSS.SDK.min.js"></script>      
        <script type="text/javascript">
            VSS.init({                        
                explicitNotifyLoaded: true,
                usePlatformStyles: true
            });

            VSS.require(["AzureDevOps/Dashboards/WidgetHelpers"], function (WidgetHelpers) {
                VSS.register("HelloWorldWidget.Configuration", function () {   
                    var $queryDropdown = $("#query-path-dropdown");

                    return {
                        load: function (widgetSettings, widgetConfigurationContext) {
                            var settings = JSON.parse(widgetSettings.customSettings.data);
                            if (settings && settings.queryPath) {
                                 $queryDropdown.val(settings.queryPath);
                             }

                             $queryDropdown.on("change", function () {
                                 var customSettings = {data: JSON.stringify({queryPath: $queryDropdown.val()})};
                                 var eventName = WidgetHelpers.WidgetEvent.ConfigurationChange;
                                 var eventArgs = WidgetHelpers.WidgetEvent.Args(customSettings);
                                 widgetConfigurationContext.notify(eventName, eventArgs);
                             });

                            return WidgetHelpers.WidgetStatusHelper.Success();
                        },
                        onSave: function() {
                            var customSettings = {data: JSON.stringify({queryPath: $queryDropdown.val()})};
                            return WidgetHelpers.WidgetConfigurationSave.Valid(customSettings); 
                        }
                    }
                });
                VSS.notifyLoadSucceeded();
            });
        </script>       
    </head>
    <body>
        <div class="container">
            <fieldset>
                <label class="label">Query: </label>
                <select id="query-path-dropdown" style="margin-top:10px">
                    <option value="" selected disabled hidden>Please select a query</option>
                    <option value="Shared Queries/Feedback">Shared Queries/Feedback</option>
                    <option value="Shared Queries/My Bugs">Shared Queries/My Bugs</option>
                    <option value="Shared Queries/My Tasks">Shared Queries/My Tasks</option>                        
                </select>
            </fieldset>     
        </div>
    </body>
</html>

步骤 4:在小组件中实现重载 - JavaScript

我们设置了小组件配置,用于存储用户选择的查询路径。 现在,我们必须更新小组件中的代码,以使用此存储的配置,而不是上一示例中的硬编码 Shared Queries/Feedback 配置。

打开 文件hello-world3.html,并在调用 VSS.register的行中将 小组件的名称从 HelloWorldWidget2 更新为 HelloWorldWidget3 。 此操作允许框架唯一标识扩展中的小组件。

通过 映射到的HelloWorldWidget3函数当前返回一个满足协定的对象IWidgetVSS.register 由于小组件现在需要配置,因此需要更新此函数以返回满足协定的对象 IConfigurableWidget 。 为此,请更新 return 语句,以包括按以下代码调用重载的属性。 此属性的值是一个函数, getQueryInfo 用于再调用方法一次。 每当用户输入更改以显示实时预览时,框架都会调用此重载方法。 保存配置时也会调用此重载方法。

return {
    load: function (widgetSettings) {
        // Set your title
        var $title = $('h2.title');
        $title.text('Hello World');

        return getQueryInfo(widgetSettings);
    },
    reload: function (widgetSettings) {
        return getQueryInfo(widgetSettings);
    }
}

中的硬编码查询路径 getQueryInfo 应替换为配置的查询路径,该路径可以从传递给 方法的参数 widgetSettings 中提取。 在方法的 getQueryInfo 开头添加以下代码,并将硬编码的查询路径替换为 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();
}

此时,小组件已准备好使用配置的设置进行呈现。

loadreload 属性都有类似的函数。 大多数简单小组件就是这种情况。 对于复杂的小组件,无论配置更改多少次,某些操作都只需要运行一次。 或者,可能有一些重量较重的操作不需要多次运行。 此类操作是对应于 属性的函数的一部分, load 而不是 属性 reload

步骤 5:更新扩展清单

打开 文件以 vss-extension.json 在 属性中包含数组的 contributions 两个新条目。 一个用于小组件, HelloWorldWidget3 另一个用于其配置。 第三个小组件需要另一个预览图像。 preview3.png将此名称命名为 ,并将其放在 img 文件夹中。 更新属性中的 files 数组,以包含在此示例中添加的两个新 HTML 文件。

{
    ...
    "contributions": [
        ... , 
        {
             "id": "HelloWorldWidget3",
             "type": "ms.vss-dashboards-web.widget",
             "targets": [
                 "ms.vss-dashboards-web.widget-catalog",
                 "fabrikam.azuredevops-extensions-myExtensions.HelloWorldWidget.Configuration"
             ],
             "properties": {
                 "name": "Hello World Widget 3 (with config)",
                 "description": "My third widget",
                 "previewImageUrl": "img/preview3.png",                       
                 "uri": "hello-world3.html",
                 "supportedSizes": [
                      {
                             "rowSpan": 1,
                             "columnSpan": 2
                         }
                     ],
                 "supportedScopes": ["project_team"]
             }
         },
         {
             "id": "HelloWorldWidget.Configuration",
             "type": "ms.vss-dashboards-web.widget-configuration",
             "targets": [ "ms.vss-dashboards-web.widget-configuration" ],
             "properties": {
                 "name": "HelloWorldWidget Configuration",
                 "description": "Configures HelloWorldWidget",
                 "uri": "configuration.html"
             }
         }
    ],
    "files": [
            {
                "path": "hello-world.html", "addressable": true
            },
             {
                "path": "hello-world2.html", "addressable": true
            },
            {
                "path": "hello-world3.html", "addressable": true
            },
            {
                "path": "configuration.html", "addressable": true
            },
            {
                "path": "sdk/scripts", "addressable": true
            },
            {
                "path": "img", "addressable": true
            }
        ],
        ...     
}

小组件配置的贡献与小组件本身略有不同。 小组件配置的贡献项具有:

  • 用于标识贡献的 ID 。 ID 在扩展中应是唯一的。
  • 贡献 的类型 。 对于所有小组件配置,它应为 ms.vss-dashboards-web.widget-configuration
  • 参与参与 的目标 数组。 对于所有小组件配置,它都有一个条目: ms.vss-dashboards-web.widget-configuration
  • 包含一组属性 的属性 ,其中包括用于配置的 HTML 文件的名称、说明和 URI。

若要支持配置,还需要更改小组件贡献。 需要更新小组件的目标数组,以将配置的 ID 包含在格式<publisher>中。><<id for the configuration contributionid for the extension>在本例中为 。fabrikam.vsts-extensions-myExtensions.HelloWorldWidget.Configuration

警告

如果可配置小组件的贡献项未按前面所述使用正确的发布者和扩展名称以配置为目标,则不会显示小组件的“配置”按钮。

在本部分结束时,清单文件应包含三个小组件和一个配置。 可以从 此处的示例获取完整的清单。

步骤 6:打包、发布和共享

如果扩展未发布,请参阅 此部分。 如果已发布扩展,可以重新打包扩展并将其直接更新到市场。

步骤 7:从目录中添加小组件

现在,转到团队仪表板 https://dev.azure.com/{Your_Organization}/{Your_Project}. 如果此页面已打开,请刷新它。 将鼠标悬停在“编辑”,然后选择“添加”。 此操作应打开小组件目录,在其中找到已安装的小组件。 若要将小组件添加到仪表板,请选择小组件,然后选择“ 添加”。

类似于下面的消息,要求你配置小组件。

概述仪表板的屏幕截图,其中包含目录中的示例小组件。

可通过两种方式配置小组件。 一种是将鼠标悬停在小组件上,选择右上角显示的省略号,然后选择“配置”。 另一种是选择仪表板右下角的“编辑”按钮,然后选择小组件右上角显示的“配置”按钮。 两者在右侧打开配置体验,并在中心预览小组件。 继续从下拉列表中选择一个查询。 实时预览显示更新的结果。 选择“保存”,小组件将显示更新的结果。

步骤 8:配置更多(可选)

可以在更多配置中 configuration.html 根据需要添加任意数量的 HTML 窗体元素。 有两个现装可用的可配置功能:小组件名称和小组件大小。

默认情况下,在扩展清单中为小组件提供的名称将存储为曾经添加到仪表板的小组件的每个实例的小组件名称。 你可以允许用户配置,以便他们可以向小组件的实例添加任何名称。 若要允许此类配置,请在扩展清单中为小组件添加 isNameConfigurable:true 属性部分。

如果在扩展清单的数组中 supportedSizes 为小组件提供多个条目,则用户也可以配置小组件的大小。

如果启用小组件名称和大小配置,本指南中第三个示例的扩展清单将如以下示例所示:

{
    ...
    "contributions": [
        ... , 
        {
             "id": "HelloWorldWidget3",
             "type": "ms.vss-dashboards-web.widget",
             "targets": [
                 "ms.vss-dashboards-web.widget-catalog",  
                 "fabrikam.azuredevops-extensions-myExtensions.HelloWorldWidget.Configuration"
             ],
             "properties": {
                 "name": "Hello World Widget 3 (with config)",
                 "description": "My third widget",
                 "previewImageUrl": "img/preview3.png",                       
                 "uri": "hello-world3.html",
                 "isNameConfigurable": true,
                 "supportedSizes": [
                    {
                        "rowSpan": 1,
                        "columnSpan": 2
                    },
                    {
                        "rowSpan": 2,
                        "columnSpan": 2
                    }
                 ],
                 "supportedScopes": ["project_team"]
             }
         },
         ...
    ]
}

使用前面的更改 ,重新打包更新 扩展。 使用 config) ) 刷新具有此小组件的仪表板 (Hello World小组件 3 (。 打开小组件的配置模式,现在应该可以看到更改小组件名称和大小的选项。

显示可以配置名称和大小的小组件的屏幕截图。

从下拉列表中选择不同的大小。 你会看到实时预览已调整大小。 保存更改,仪表板上的小组件也会调整大小。

更改小组件的名称不会导致小组件的任何可见更改,因为我们的示例小组件不会在任何位置显示小组件名称。 让我们修改示例代码以显示小组件名称,而不是硬编码文本“Hello World”。

为此,请将硬编码文本“Hello World”替换为 widgetSettings.name 设置元素文本的 h2 行。 此操作可确保每次在页面刷新时加载小组件时显示小组件名称。 由于我们希望在每次配置更改时更新实时预览版,因此我们也应该在 reload 代码部分添加相同的代码。 中的 hello-world3.html 最终返回语句如下所示:

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);
    }
}

重新打包 并再次 更新 扩展。 刷新具有此小组件的仪表板。

在配置模式下,对小组件名称所做的任何更改,立即更新小组件标题。