添加仪表板小组件

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

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

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

提示

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

本教程的准备和所需设置

若要为 Azure DevOps 或 TFS 创建扩展,需要一些必备软件和工具:

知识: 小组件开发需要一些 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. 本指南的第一部分介绍如何创建新小组件,该小组件打印简单的“Hello World”消息。
  2. 二部分 在第一部分的基础上构建,方法是添加对 Azure DevOps REST API 的调用。
  3. 三部分 介绍如何向小组件添加配置。

注意

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

开始使用我们现成提供的 一些小组件的基本样式 ,以及有关小组件结构的一些指南。

第 1 部分:Hello World

此部分演示了一个使用 JavaScript 打印“Hello World”的小组件。

Overview dashboard with a sample widget

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

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

使用“npm install”命令检索 SDK:

npm install vss-web-extension-sdk

若要详细了解 SDK,请访问 客户端 SDK GitHub 页面

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

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

小组件基于 HTML,托管在 iframe 中。 在 中添加以下 HTML hello-world.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 ,以便小组件可以使用 html 元素的 Azure DevOps 核心样式 (正文、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

若要支持 TFS 2015 Update 3,需要一个 330 像素 x 160 像素的额外映像。 此预览图像显示在此目录中。 选择一个图像,将其 preview.png命名为 ,并像以前一样将其 img 放在 文件夹中。

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

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

  • 每个 扩展都必须有一个扩展清单文件
  • 读取 扩展清单参考
  • 在扩展点中详细了解贡献

(vss-extension.json创建 json 文件,例如,在 home 目录中创建包含以下内容的) :

    {
        "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
            }
        ]
    }

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

注意

此处的 发布者 需要更改为发布者名称。 若要立即创建发布者,请访问 包/发布/安装

图标

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

参与

每个贡献项都定义 属性

  • 用于标识贡献的 ID 。 此 ID 在扩展中应是唯一的。 此 ID 应与 步骤 3 中用于注册小组件的名称匹配。
  • 贡献 的类型 。 对于所有小组件,类型应为 ms.vss-dashboards-web.widget
  • 参与参与 的目标 数组。 对于所有小组件,目标应为 [ms.vss-dashboards-web.widget-catalog]
  • 属性是包含贡献类型的属性的对象。 对于小组件,以下属性是必需的。
属性 说明
name 要显示在小组件目录中的小组件的名称。
description 要显示在小组件目录中的小组件的说明。
catalogIconUrl 步骤 4 中添加的要在小组件目录中显示的目录图标的相对路径。 图像应为 98 像素 x 98 像素。 如果已使用不同的文件夹结构或不同的文件名,请在此处指定相应的相对路径。
previewImageUrl 步骤 4 中添加的预览图像的相对路径,该图像仅显示在 TFS 2015 Update 3 的小组件目录中。 图像应为 330 像素 x 160 像素。 如果已使用不同的文件夹结构或不同的文件名,请在此处指定相应的相对路径。
uri 步骤 1 中添加的 HTML 文件的相对路径。 如果已使用不同的文件夹结构或不同的文件名,请在此处指定相应的相对路径。
supportedSizes 小组件支持的大小数组。 当小组件支持多个大小时,数组中的第一个大小是小组件的默认大小。 widget size为仪表板网格中小组件占用的行和列指定 。 一行/列对应于 160 px。 高于 1x1 的任何维度都会获得一个额外的 10 px,表示小组件之间的装订线。 例如,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 fabrikam 替换为发布者 ID。

发布和共享扩展

创建发布者后,现在可以将扩展上传到市场。

  1. 找到 “上传新扩展 ”按钮,导航到打包的 .vsix 文件,然后选择“ 上传”。

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

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

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

  1. 在 Azure DevOps 中转到项目, http://dev.azure.com/{yourOrganization}/{yourProject}

  2. 选择“ 概述”,然后选择“ 仪表板”。

  3. 选择 “添加小组件”。

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

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

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

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

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

步骤 1:HTML

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

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

在“h2”下方添加新的“div”元素以保存查询信息。 在调用“VSS.register”的行中,将小组件的名称从“HelloWorldWidget2”更新为“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"
    ]
}

警告

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

步骤 3:进行 REST API 调用

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

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

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

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

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

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

如果文件夹下Shared Queries没有Feedback查询,请将代码中的 替换为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($('- ').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);

最后 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(["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>

步骤 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/{yourOrganization}/{yourProject} 如果此页面已打开,请刷新它。 将鼠标悬停在右下角的“编辑”按钮上,然后选择“添加”按钮。 此时会打开小组件目录,可在其中找到已安装的小组件。 选择小组件,然后选择“添加”按钮将其添加到仪表板。

第 3 部分:使用配置Hello World

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

Overview dashboard live preview of the widget based on changes.

步骤 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("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.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("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>

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

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

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

通过 映射到的HelloWorldWidget3函数当前返回一个满足协定的对象IWidgetVSS.register 由于小组件现在需要配置,因此需要更新此函数以返回满足协定的对象 IConfigurableWidget 。 为此,请更新 return 语句以包含名为 reload 的属性,如下所示。 此属性的值是一个函数, 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”中的硬编码查询路径替换为配置的查询路径,该路径可以从传递给该方法的参数“widget设置”中提取。 在“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.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
            }
        ],
        ...     
}

请注意,小组件配置的贡献遵循的模型与小组件本身略有不同。 小组件配置的贡献条目有:
  • 用于标识贡献的 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/{yourOrganization}/{yourProject}. 如果此页面已打开,请刷新它。 将鼠标悬停在右下角的“编辑”按钮上,然后选择“添加”按钮。 这会打开小组件目录,可在其中找到你安装的小组件。 选择小组件,然后选择“添加”按钮将其添加到仪表板。

你将看到一条消息,要求你配置小组件。

Overview dashboard with a sample widget from the catalog.

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

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

可以根据需要在 中添加任意数量的 HTML 表单元素, configuration.html 以便进行其他配置。 有两个现成可用的可配置功能:小组件名称和小组件大小。

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

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

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

{
    ...
    "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"]
             }
         },
         ...
}

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

Widget where name and size can be configured

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

警告

如果删除已支持的大小,则无法正确加载小组件。 我们正在为将来的版本进行修复。

更改小组件的名称不会导致小组件中的任何可见更改。 这是因为示例小组件不会在任何地方显示小组件名称。 让我们修改示例代码以显示小组件名称,而不是硬编码文本“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);
    }
}

重新打包 并再次 更新 扩展。 刷新具有此小组件的仪表板。 在配置模式下,对小组件名称所做的任何更改,立即更新小组件标题。