Azure DevOps Services |Azure DevOps Server |Azure DevOps Server 2022 |Azure DevOps Server 2020
小组件作为贡献在扩展框架中实现。 单个扩展可以包含多个小组件贡献。 本文介绍如何创建提供一个或多个小组件的扩展。
提示
查看有关使用 Azure DevOps 扩展 SDK 进行扩展开发的最新文档。
提示
如果要启动新的 Azure DevOps 扩展,请先尝试这些维护的示例集合 -- 它们适用于当前产品版本并涵盖新式方案(例如,在拉取请求页上添加选项卡)。
- Azure DevOps 扩展示例 (GitHub)-一个演示常见扩展模式的紧凑初学者示例: https://github.com/microsoft/azure-devops-extension-sample
- Azure DevOps 扩展样例(旧版集合和贡献指南),安装以检查用户界面目标或查看源:https://marketplace.visualstudio.com/items/ms-samples.samples-contributions-guide 和 https://github.com/Microsoft/vso-extension-samples/tree/master/contributions-guide
- Microsoft Learn 示例(浏览 Azure DevOps 示例)- 跨 Microsoft 文档的精心挑选的、最新的示例:/samples/browse/?terms=azure%20devops%20extension
如果某个示例在组织中不起作用,请将其安装到个人或测试组织中,并将扩展清单的目标 ID 和 API 版本与当前文档进行比较。有关参考和 API,请参阅:
先决条件
| 要求 | 说明 |
|---|---|
| 编程知识 | 用于小部件开发的 JavaScript、HTML 和 CSS 知识 |
| Azure DevOps 组织 | 创建组织 |
| 文本编辑器 | 我们将 Visual Studio Code 用于教程 |
| Node.js | 最新版本的 Node.js |
| 跨平台 CLI |
tfx-cli,用于打包扩展 安装使用: npm i -g tfx-cli |
| 项目目录 | 完成本教程后,主目录的结构为:|--- README.md|--- sdk |--- node_modules |--- scripts |--- VSS.SDK.min.js|--- img |--- logo.png|--- scripts|--- hello-world.html // html page for your widget|--- vss-extension.json // extension manifest |
教程概览
本教程通过三个递进示例教授小部件开发:
| 部件 | 焦点 | 学习内容 |
|---|---|---|
| 第 1 部分:Hello World | 创建基本控件 | 创建显示文本的小组件 |
| 第 2 部分:REST API 集成 | Azure DevOps API 调用 | 添加 REST API 功能来提取和显示数据 |
| 第 3 部分:窗口小部件配置 | 用户自定义 | 实现小组件的配置选项 |
提示
包含的示例(请参阅上一条注释)显示了一组可以打包和发布的控件,如果希望直接跳转到实际示例。
在开始之前,查看我们提供的基本小组件样式和结构指南。
第 1 部分:Hello World
使用 JavaScript 创建显示“Hello World”的基本控件。 此基础演示了核心小组件开发概念。
步骤 1:安装客户端 SDK
利用 VSS SDK,小组件可与 Azure DevOps 进行通信。 使用 npm 安装它:
npm install vss-web-extension-sdk
将 VSS.SDK.min.js 文件从 vss-web-extension-sdk/lib 文件夹复制到文件夹 home/sdk/scripts 。
有关更多 SDK 文档,请参阅 客户端 SDK GitHub 页。
步骤 2:创建 HTML 结构
在项目目录中创建 hello-world.html 。 此文件提供小部件的布局信息和对所需脚本的引用信息。
<!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>
小组件在 iframe 中运行,因此除 <script> 和 <link> 外,大多数 HTML 头元素都会被框架忽略。
步骤 3:添加小组件 JavaScript
若要实现小组件功能,请将此脚本添加到 HTML 文件的 <head> 部分。
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers"], function (WidgetHelpers) {
WidgetHelpers.IncludeWidgetStyles();
VSS.register("HelloWorldWidget", function () {
return {
load: function (widgetSettings) {
var $title = $('h2.title');
$title.text('Hello World');
return WidgetHelpers.WidgetStatusHelper.Success();
}
};
});
VSS.notifyLoadSucceeded();
});
</script>
关键 JavaScript 组件
| 功能 | 目的 |
|---|---|
VSS.init() |
初始化小组件与 Azure DevOps 之间的通信 |
VSS.require() |
加载所需的 SDK 库和小部件助手 |
VSS.register() |
使用唯一标识符注册小组件 |
WidgetHelpers.IncludeWidgetStyles() |
应用默认的 Azure DevOps 样式 |
VSS.notifyLoadSucceeded() |
通知框架加载成功完成 |
重要
VSS.register() 中的小组件名称必须与扩展清单中的 id 匹配(步骤 5)。
步骤 4:添加扩展映像
为你的扩展创建所需的图像:
-
扩展徽标:98x98 像素图像,命名为
logo.png,位于img文件夹中 -
小组件目录图标:
CatalogIcon.png文件夹中名为img的 98x98 像素图像 -
小组件预览:
preview.png文件夹中名为img的 330x160 像素图像
当用户浏览可用扩展时,这些图像将显示在应用市场和组件目录中。
步骤 5:创建扩展清单
在项目的根目录中创建 vss-extension.json 。 此文件定义扩展的元数据和贡献:
{
"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
}
]
}
重要
将 "publisher": "fabrikam" 替换为您的实际发布者名称。 了解如何 创建发布者。
基本清单属性
| 部分 | 目的 |
|---|---|
| 基本信息 | 扩展名称、版本、说明和发布者 |
| 图标 | 扩展视觉资产的路径 |
| 贡献 | 小组件定义,包括 ID、类型和属性 |
| 文件 | 要包含在扩展包中的所有文件 |
有关完整的清单文档,请参阅 扩展清单参考。
步骤 6:打包和发布扩展
打包扩展并将其发布到 Visual Studio 市场。
安装打包工具
npm i -g tfx-cli
创建扩展包
从项目目录中运行:
tfx extension create --manifest-globs vss-extension.json
此操作将创建包含已打包扩展的 .vsix 文件。
设置发布者
- 转到 Visual Studio Marketplace 发布门户。
- 如果没有发布者,请登录并创建发布者。
- 选择唯一的发布者标识符(在清单文件中使用)。
- 将
vss-extension.json更新为发布者名称,而不是“fabrikam”。
上传扩展
- 在发布门户中,选择“ 上传新扩展”。
- 选择文件
.vsix并上传它。 - 与 Azure DevOps 组织共享扩展。
或者,使用命令行:
tfx extension publish --manifest-globs vss-extension.json --share-with yourOrganization
提示
用于 --rev-version 在更新现有扩展时自动递增版本号。
步骤 7:安装和测试小组件
若要进行测试,请将小组件添加到仪表板:
- 转到 Azure DevOps 项目:
https://dev.azure.com/{Your_Organization}/{Your_Project} - 转到概述>仪表板。
- 选择“添加小组件”。
- 在目录中找到小组件,然后选择“添加”。
“Hello World”小组件显示在仪表板上,其中显示配置的文本。
下一步:继续学习第 2 部分 ,了解如何将 Azure DevOps REST API 集成到小组件中。
第 2 部分:使用 Azure DevOps REST API 实现“你好,世界”
将小组件扩展为使用 REST API 与 Azure DevOps 数据进行交互。 此示例演示如何提取查询信息并在小组件中动态显示信息。
在本部分中,使用 工作项跟踪 REST API 检索有关现有查询的信息,并在“Hello World”文本下方显示查询详细信息。
步骤 1:创建增强的 HTML 文件
创建一个在上一个示例基础上构建的新挂件文件。 复制 hello-world.html 并将其重命名为 hello-world2.html。 项目结构现在包括:
|--- README.md
|--- node_modules
|--- sdk/
|--- scripts/
|--- VSS.SDK.min.js
|--- img/
|--- logo.png
|--- scripts/
|--- hello-world.html // Part 1 widget
|--- hello-world2.html // Part 2 widget (new)
|--- vss-extension.json // Extension manifest
更新小组件 HTML 结构
对hello-world2.html进行这些更改:
-
添加查询数据的容器:包括用于显示查询信息的新
<div>元素。 -
更新小组件标识符:将小组件名称从
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(["AzureDevOps/Dashboards/WidgetHelpers"], function (WidgetHelpers) {
WidgetHelpers.IncludeWidgetStyles();
VSS.register("HelloWorldWidget2", function () {
return {
load: function (widgetSettings) {
var $title = $('h2.title');
$title.text('Hello World');
return WidgetHelpers.WidgetStatusHelper.Success();
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
</head>
<body>
<div class="widget">
<h2 class="title"></h2>
<div id="query-info-container"></div>
</div>
</body>
</html>
步骤 2:配置 API 访问权限
在进行 REST API 调用之前,请在扩展清单中配置所需的权限。
添加工作范围
vso.work 范围授予对工作项和查询的只读访问权限。 将此范围添加到vss-extension.json中。
{
"scopes": [
"vso.work"
]
}
完整清单示例
对于具有其他属性的完整清单,请按如下所示对其进行结构:
{
"name": "example-widget",
"publisher": "example-publisher",
"version": "1.0.0",
"scopes": [
"vso.work"
]
}
重要
范围限制:不支持在发布后添加或更改范围。 如果已发布扩展,必须先将其从市场中删除。 转到 Visual Studio Marketplace 发布门户,找到扩展,然后选择“ 删除”。
步骤 3:实现 REST API 集成
Azure DevOps 通过 SDK 提供 JavaScript REST 客户端库。 这些库包装 AJAX 调用并将 API 响应映射到可用对象。
更新小组件 JavaScript
替换 VSS.require 中的 hello-world2.html 调用,以包含工作项跟踪 REST 客户端:
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers", "AzureDevOps/WorkItemTracking/RestClient"],
function (WidgetHelpers, WorkItemTrackingRestClient) {
WidgetHelpers.IncludeWidgetStyles();
VSS.register("HelloWorldWidget2", function () {
var projectId = VSS.getWebContext().project.id;
var getQueryInfo = function (widgetSettings) {
// Get a WIT client to make REST calls to Azure DevOps Services
return WorkItemTrackingRestClient.getClient().getQuery(projectId, "Shared Queries/Feedback")
.then(function (query) {
// Process query data (implemented in Step 4)
return WidgetHelpers.WidgetStatusHelper.Success();
}, function (error) {
return WidgetHelpers.WidgetStatusHelper.Failure(error.message);
});
}
return {
load: function (widgetSettings) {
// Set your title
var $title = $('h2.title');
$title.text('Hello World');
return getQueryInfo(widgetSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
关键实现详细信息
| 组件 | 目的 |
|---|---|
WorkItemTrackingRestClient.getClient() |
获取工作项跟踪 REST 客户端的实例 |
getQuery() |
检索承诺中包装的查询信息 |
WidgetStatusHelper.Failure() |
为小组件故障提供一致的错误处理 |
projectId |
API 调用所需的当前项目上下文 |
提示
自定义查询路径:如果在“共享查询”中没有“反馈”查询,请将 "Shared Queries/Feedback" 替换为项目中任何现有查询的路径。
步骤 4:显示 API 响应数据
通过处理 REST API 响应,在小组件中呈现查询信息。
添加查询数据呈现功能
用此实现替换 // Process query data 注释:
// 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);
该方法 getQuery() 返回一个 Contracts.QueryHierarchyItem 对象,其中包含查询元数据的属性。 本示例显示“Hello World”文本下方的三个关键信息片段。
完整工作示例
最终 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, WorkItemTrackingRestClient) {
WidgetHelpers.IncludeWidgetStyles();
VSS.register("HelloWorldWidget2", function () {
var projectId = VSS.getWebContext().project.id;
var getQueryInfo = function (widgetSettings) {
// Get a WIT client to make REST calls to Azure DevOps Services
return WorkItemTrackingRestClient.getClient().getQuery(projectId, "Shared Queries/Feedback")
.then(function (query) {
// Create a list with query details
var $list = $('<ul>');
$list.append($('<li>').text("Query ID: " + query.id));
$list.append($('<li>').text("Query Name: " + query.name));
$list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName : "<unknown>")));
// Append the list to the query-info-container
var $container = $('#query-info-container');
$container.empty();
$container.append($list);
// Use the widget helper and return success as Widget Status
return WidgetHelpers.WidgetStatusHelper.Success();
}, function (error) {
// Use the widget helper and return failure as Widget Status
return WidgetHelpers.WidgetStatusHelper.Failure(error.message);
});
}
return {
load: function (widgetSettings) {
// Set your title
var $title = $('h2.title');
$title.text('Hello World');
return getQueryInfo(widgetSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
</head>
<body>
<div class="widget">
<h2 class="title"></h2>
<div id="query-info-container"></div>
</div>
</body>
</html>
步骤 5:更新扩展清单
若要使其在小组件目录中可用,请将新小组件添加到扩展清单。
添加第二个小组件贡献
将 vss-extension.json 更新为包含启用 REST API 的小组件。 将此贡献添加到 contributions 数组:
{
"contributions": [
// ...existing HelloWorldWidget contribution...,
{
"id": "HelloWorldWidget2",
"type": "ms.vss-dashboards-web.widget",
"targets": [
"ms.vss-dashboards-web.widget-catalog"
],
"properties": {
"name": "Hello World Widget 2 (with API)",
"description": "My second widget",
"previewImageUrl": "img/preview2.png",
"uri": "hello-world2.html",
"supportedSizes": [
{
"rowSpan": 1,
"columnSpan": 2
}
],
"supportedScopes": ["project_team"]
}
}
],
"files": [
{
"path": "hello-world.html",
"addressable": true
},
{
"path": "hello-world2.html",
"addressable": true
},
{
"path": "sdk/scripts",
"addressable": true
},
{
"path": "img",
"addressable": true
}
],
"scopes": [
"vso.work"
]
}
提示
预览图像:创建 preview2.png 图像(330x160 像素),并将其 img 放置在文件夹中,向用户显示小组件在目录中的外观。
步骤 6:打包、发布和共享
打包、发布和共享你的扩展。 如果已发布扩展,则可以在市场中直接重新打包和更新它。
步骤 7:测试您的 REST API 组件
为了查看 REST API 集成的实际效果,请将新部件添加到您的仪表板中。
- 转到 Azure DevOps 项目:
https://dev.azure.com/{Your_Organization}/{Your_Project} - 选择概述>仪表板。
- 选择“添加小组件”。
- 找到“Hello World 小工具 2(包含 API)”,然后选择 添加。
您的增强窗口小部件显示了 "Hello World" 文本以及来自您的 Azure DevOps 项目的实时查询信息。
后续步骤:继续执行 第 3 部分 以添加配置选项,让用户自定义要显示的查询。
第 3 部分:配置 Hello World
通过将用户配置功能添加到小组件,利用第 2 部分。 无需对查询路径进行硬编码,而是创建一个配置界面,让用户选择要显示的查询,并具有实时预览功能。
本部分演示如何创建可配置的小组件,用户可以根据特定需求进行自定义,同时在配置期间提供实时反馈。
步骤 1:创建配置文件
小组件配置与小组件本身共享许多相似之处-它们都使用相同的 SDK、HTML 结构和 JavaScript 模式,但在扩展框架中提供不同的用途。
设置项目结构
若要支持小组件配置,请创建两个新文件:
- 复制
hello-world2.html并将其重命名为hello-world3.html,即可配置的小组件。 - 创建名为
configuration.html的新文件,用于处理配置接口。
项目结构现在包括:
|--- README.md
|--- sdk/
|--- node_modules
|--- scripts/
|--- VSS.SDK.min.js
|--- img/
|--- logo.png
|--- scripts/
|--- configuration.html // New: Configuration interface
|--- hello-world.html // Part 1: Basic widget
|--- hello-world2.html // Part 2: REST API widget
|--- hello-world3.html // Part 3: Configurable widget (new)
|--- vss-extension.json // Extension manifest
创建配置接口
将此 HTML 结构添加到 configuration.html其中,这将创建用于选择查询的下拉列表选择器:
<!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 遵循与小组件相同的初始化模式,但实现 IWidgetConfiguration 合同,而不是基本 IWidget 合同。
添加配置逻辑
将此脚本插入到 <head> 和 configuration.html 之间的部分中:
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers"], function (WidgetHelpers) {
VSS.register("HelloWorldWidget.Configuration", function () {
var $queryDropdown = $("#query-path-dropdown");
return {
load: function (widgetSettings, widgetConfigurationContext) {
var settings = JSON.parse(widgetSettings.customSettings.data);
if (settings && settings.queryPath) {
$queryDropdown.val(settings.queryPath);
}
return WidgetHelpers.WidgetStatusHelper.Success();
},
onSave: function() {
var customSettings = {
data: JSON.stringify({
queryPath: $queryDropdown.val()
})
};
return WidgetHelpers.WidgetConfigurationSave.Valid(customSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
配置协定详细信息
协定 IWidgetConfiguration 需要以下关键功能:
| 功能 | 目的 | 在调用时 |
|---|---|---|
load() |
使用现有设置初始化配置 UI | “配置”对话框打开时 |
onSave() |
序列化用户输入和验证设置 | 当用户选择“保存”时 |
提示
数据序列化:此示例使用 JSON 序列化设置。 小组件通过 widgetSettings.customSettings.data 访问这些设置,并且必须相应地对这些设置进行反序列化。
步骤 3:启用实时预览功能
实时预览允许用户在修改配置设置时立即查看小组件更改,在保存之前提供即时反馈。
实现更改通知
若要启用实时预览,请在 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:将小组件设为可配置
将第 2 部分中的小组件转换为使用配置数据,而不是硬编码值。 此步骤需要实现 IConfigurableWidget 协定。
更新小组件注册
在 hello-world3.html中进行以下更改:
-
更新小组件 ID:从
HelloWorldWidget2更改为HelloWorldWidget3。 -
添加重载函数:实现
IConfigurableWidget协定。
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 以使用配置设置,而不是硬编码的查询路径:
var settings = JSON.parse(widgetSettings.customSettings.data);
if (!settings || !settings.queryPath) {
var $container = $('#query-info-container');
$container.empty();
$container.text("Please configure a query path to display data.");
return WidgetHelpers.WidgetStatusHelper.Success();
}
小组件生命周期差异
| 功能 | 目的 | 使用指南 |
|---|---|---|
load() |
初始小组件呈现和一次性设置 | 大量操作,资源初始化 |
reload() |
使用新配置更新小组件 | 轻量级更新,数据刷新 |
提示
性能优化:对于只需运行一次的昂贵操作,使用load(),而对于配置变更时的快速更新,使用reload()。
(可选)添加灯箱以获取详细信息
仪表板小组件的空间有限,因此很难显示全面的信息。 灯箱通过在模态叠加层中显示详细数据,而无需离开仪表板,提供一种优雅的解决方案。
为什么在小组件中使用光盒?
| 益处 | 说明 |
|---|---|
| 空间效率 | 保持小组件紧凑,同时提供详细视图 |
| 用户体验 | 显示详细信息时保持仪表板上下文 |
| 渐进式披露 | 在组件中显示摘要数据,详细信息可按需获取 |
| 响应式设计 | 根据不同的屏幕大小和小组件配置进行调整 |
实现可单击元素
更新查询数据呈现,以包含可单击的元素来触发灯箱:
// Create a list with clickable query details
var $list = $('<ul class="query-summary">');
$list.append($('<li>').text("Query ID: " + query.id));
$list.append($('<li>').text("Query Name: " + query.name));
$list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName : "<unknown>"));
// Add a clickable element to open detailed view
var $detailsLink = $('<button class="details-link">View Details</button>');
$detailsLink.on('click', function() {
showQueryDetails(query);
});
// Append to the container
var $container = $('#query-info-container');
$container.empty();
$container.append($list);
$container.append($detailsLink);
创建灯箱功能
将此光盒实现添加到小组件 JavaScript:
function showQueryDetails(query) {
// Create lightbox overlay
var $overlay = $('<div class="lightbox-overlay">');
var $lightbox = $('<div class="lightbox-content">');
// Add close button
var $closeBtn = $('<button class="lightbox-close">×</button>');
$closeBtn.on('click', function() {
$overlay.remove();
});
// Create detailed content
var $content = $('<div class="query-details">');
$content.append($('<h3>').text(query.name || 'Query Details'));
$content.append($('<p>').html('<strong>ID:</strong> ' + query.id));
$content.append($('<p>').html('<strong>Path:</strong> ' + query.path));
$content.append($('<p>').html('<strong>Created:</strong> ' + (query.createdDate ? new Date(query.createdDate).toLocaleDateString() : 'Unknown')));
$content.append($('<p>').html('<strong>Modified:</strong> ' + (query.lastModifiedDate ? new Date(query.lastModifiedDate).toLocaleDateString() : 'Unknown')));
$content.append($('<p>').html('<strong>Created By:</strong> ' + (query.createdBy ? query.createdBy.displayName : 'Unknown')));
$content.append($('<p>').html('<strong>Modified By:</strong> ' + (query.lastModifiedBy ? query.lastModifiedBy.displayName : 'Unknown')));
if (query.queryType) {
$content.append($('<p>').html('<strong>Type:</strong> ' + query.queryType));
}
// Assemble lightbox
$lightbox.append($closeBtn);
$lightbox.append($content);
$overlay.append($lightbox);
// Add to document and show
$('body').append($overlay);
// Close on overlay click
$overlay.on('click', function(e) {
if (e.target === $overlay[0]) {
$overlay.remove();
}
});
// Close on Escape key
$(document).on('keydown.lightbox', function(e) {
if (e.keyCode === 27) { // Escape key
$overlay.remove();
$(document).off('keydown.lightbox');
}
});
}
添加灯箱样式
在小组件 HTML <head> 部分中包含光盒的 CSS 样式:
<style>
.query-summary {
list-style: none;
padding: 0;
margin: 10px 0;
}
.query-summary li {
padding: 2px 0;
font-size: 12px;
}
.details-link {
background: #0078d4;
color: white;
border: none;
padding: 4px 8px;
font-size: 11px;
cursor: pointer;
border-radius: 2px;
margin-top: 8px;
}
.details-link:hover {
background: #106ebe;
}
.lightbox-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
}
.lightbox-content {
background: white;
border-radius: 4px;
padding: 20px;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
position: relative;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.lightbox-close {
position: absolute;
top: 10px;
right: 15px;
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #666;
line-height: 1;
}
.lightbox-close:hover {
color: #000;
}
.query-details h3 {
margin-top: 0;
color: #323130;
}
.query-details p {
margin: 8px 0;
font-size: 14px;
line-height: 1.4;
}
</style>
增强的小组件实现
具有光盒功能的全面增强小组件:
<!DOCTYPE html>
<html>
<head>
<script src="sdk/scripts/VSS.SDK.min.js"></script>
<style>
/* Lightbox styles from above */
.query-summary {
list-style: none;
padding: 0;
margin: 10px 0;
}
.query-summary li {
padding: 2px 0;
font-size: 12px;
}
.details-link {
background: #0078d4;
color: white;
border: none;
padding: 4px 8px;
font-size: 11px;
cursor: pointer;
border-radius: 2px;
margin-top: 8px;
}
.details-link:hover {
background: #106ebe;
}
.lightbox-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
}
.lightbox-content {
background: white;
border-radius: 4px;
padding: 20px;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
position: relative;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}
.lightbox-close {
position: absolute;
top: 10px;
right: 15px;
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #666;
line-height: 1;
}
.lightbox-close:hover {
color: #000;
}
.query-details h3 {
margin-top: 0;
color: #323130;
}
.query-details p {
margin: 8px 0;
font-size: 14px;
line-height: 1.4;
}
</style>
<script type="text/javascript">
VSS.init({
explicitNotifyLoaded: true,
usePlatformStyles: true
});
VSS.require(["AzureDevOps/Dashboards/WidgetHelpers", "AzureDevOps/WorkItemTracking/RestClient"],
function (WidgetHelpers, WorkItemTrackingRestClient) {
WidgetHelpers.IncludeWidgetStyles();
function showQueryDetails(query) {
// Lightbox implementation from above
}
VSS.register("HelloWorldWidget2", function () {
var projectId = VSS.getWebContext().project.id;
var getQueryInfo = function (widgetSettings) {
return WorkItemTrackingRestClient.getClient().getQuery(projectId, "Shared Queries/Feedback")
.then(function (query) {
// Enhanced display with lightbox trigger
var $list = $('<ul class="query-summary">');
$list.append($('<li>').text("Query ID: " + query.id));
$list.append($('<li>').text("Query Name: " + query.name));
$list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName : "<unknown>")));
var $detailsLink = $('<button class="details-link">View Details</button>');
$detailsLink.on('click', function() {
showQueryDetails(query);
});
var $container = $('#query-info-container');
$container.empty();
$container.append($list);
$container.append($detailsLink);
return WidgetHelpers.WidgetStatusHelper.Success();
}, function (error) {
return WidgetHelpers.WidgetStatusHelper.Failure(error.message);
});
}
return {
load: function (widgetSettings) {
// Set your title
var $title = $('h2.title');
$title.text('Hello World');
return getQueryInfo(widgetSettings);
}
}
});
VSS.notifyLoadSucceeded();
});
</script>
</head>
<body>
<div class="widget">
<h2 class="title"></h2>
<div id="query-info-container"></div>
</div>
</body>
</html>
辅助功能注意事项:确保灯箱易于键盘访问,并为屏幕阅读器提供适当的标签。 使用 Azure DevOps 的内置辅助功能进行测试。
重要
性能:灯箱应快速加载。 考虑仅在光盒打开时才延迟加载详细数据,而不是提前提取所有数据。
步骤 5:配置扩展清单
在扩展清单中注册可配置组件及其配置界面。
添加小组件和配置贡献
更新 vss-extension.json 以包括两个新贡献:
{
"contributions": [
{
"id": "HelloWorldWidget3",
"type": "ms.vss-dashboards-web.widget",
"targets": [
"ms.vss-dashboards-web.widget-catalog",
"fabrikam.azuredevops-extensions-myExtensions.HelloWorldWidget.Configuration"
],
"properties": {
"name": "Hello World Widget 3 (with config)",
"description": "My third widget",
"previewImageUrl": "img/preview3.png",
"uri": "hello-world3.html",
"supportedSizes": [
{
"rowSpan": 1,
"columnSpan": 2
},
{
"rowSpan": 2,
"columnSpan": 2
}
],
"supportedScopes": ["project_team"]
}
},
{
"id": "HelloWorldWidget.Configuration",
"type": "ms.vss-dashboards-web.widget-configuration",
"targets": [ "ms.vss-dashboards-web.widget-configuration" ],
"properties": {
"name": "HelloWorldWidget Configuration",
"description": "Configures HelloWorldWidget",
"uri": "configuration.html"
}
}
],
"files": [
{
"path": "hello-world.html", "addressable": true
},
{
"path": "hello-world2.html", "addressable": true
},
{
"path": "hello-world3.html", "addressable": true
},
{
"path": "configuration.html", "addressable": true
},
{
"path": "sdk/scripts", "addressable": true
},
{
"path": "img", "addressable": true
}
]
}
配置贡献要求
| 属性 | 目的 | 所需的值 |
|---|---|---|
type |
将贡献识别为小组件配置 | ms.vss-dashboards-web.widget-configuration |
targets |
显示配置的位置 | ms.vss-dashboards-web.widget-configuration |
uri |
配置 HTML 文件的路径 | 配置文件路径 |
小组件定位模式
对于可配置的小组件,targets 数组必须包含配置引用:
<publisher>.<extension-id>.<configuration-id>
警告
配置按钮可见性:如果小组件未正确定位其配置贡献,则不会显示 “配置 ”按钮。 验证发布者和扩展名称是否与清单完全匹配。
步骤 6:打包、发布和共享
使用配置功能部署增强型扩展。
如果是第一个发布,请按照 步骤 6:打包、发布和共享。 对于现有扩展,请直接在市场中重新打包和更新。
步骤 7:测试可配置小组件
通过添加和配置微件来体验完整的配置流程。
将小组件添加到仪表板
- 转到
https://dev.azure.com/{Your_Organization}/{Your_Project}。 - 转到概述>仪表板。
- 选择“添加小组件”。
- 找到“Hello World 组件 3(带配置)”,然后选择添加。
因为小组件需要设置,所以此时会显示配置提示:
概览仪表板的屏幕截图,其中包含一个来自目录的示例组件。
配置小组件
通过任一方法访问配置:
- 小组件菜单:将鼠标悬停在小组件上,选择省略号 (?),然后选择 “配置”
- 仪表板编辑模式:在仪表板上选择编辑,然后在微件上选择配置按钮
此时会打开配置面板,并在中心内提供实时预览。 从下拉列表中选择一个查询以查看即时更新,然后选择“ 保存” 以应用更改。
步骤 8:添加高级配置选项
使用更多内置配置功能(如自定义名称和大小)扩展小组件。
启用名称和大小配置选项
Azure DevOps 提供两个现装的可配置功能:
| 功能 / 特点 | 清单属性 | 目的 |
|---|---|---|
| 自定义名称 | 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"]
}
}
]
}
显示配置的名称
若要显示自定义小组件名称,请更新小组件以使用 widgetSettings.name:
return {
load: function (widgetSettings) {
// Display configured name instead of hard-coded text
var $title = $('h2.title');
$title.text(widgetSettings.name);
return getQueryInfo(widgetSettings);
},
reload: function (widgetSettings) {
// Update name during configuration changes
var $title = $('h2.title');
$title.text(widgetSettings.name);
return getQueryInfo(widgetSettings);
}
}
更新扩展后,可以配置小组件名称和大小:
祝贺! 你创建了一个完整的可配置 Azure DevOps 仪表板小组件,其中包含实时预览功能和用户自定义选项。