共用方式為


新增儀錶板小工具

Azure DevOps 服務 |Azure DevOps Server |Azure DevOps Server 2022 |Azure DevOps Server 2020

小部件在擴展框架中作為貢獻來實現。 單一擴充套件可以包含多個小工具功能。 本文說明如何建立提供一或多個小工具的擴充功能。

提示

請參閱使用 Azure DevOps 擴充功能 SDK 開發擴充功能的最新文件。

提示

如果您要啟動新的 Azure DevOps 延伸模組,請先嘗試這些維護的範例集合,它們可與目前的產品組建搭配使用,並涵蓋新式案例 (例如,在提取要求頁面上新增索引標籤) 。

如果範例在您的組織中無法運作,請將它安裝到個人或測試組織中,並將擴充功能資訊清單的目標識別碼和 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 可讓您的 Widget 與 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:新增 Widget JavaScript

若要實作小工具功能,請將此腳本新增至 <head> HTML 檔案的 區段:

<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() 初始化 Widget 與 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 像素影像。

當使用者流覽可用的擴充功能時,這些影像會顯示在 Marketplace 和 Widget 目錄中。

步驟 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" 替換為實際的發行者名稱。 瞭解如何 建立發行者

基本清單屬性

章節 目標
基本資訊 延伸模組名稱、版本、描述和發行者
圖示 延伸模組視覺資產的路徑
貢獻 小工具定義,包括識別碼、類型和屬性
檔案 要包含在擴充套件中的所有檔案

如需完整的指令清單檔,請參閱 延伸模組指令清單參考

步驟 6:封裝和發佈您的擴充功能

封裝您的延伸模組,並將其發佈至 Visual Studio Marketplace。

安裝封裝工具

npm i -g tfx-cli

建立延伸模組套件

從您的項目目錄,執行:

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

此動作會建立 .vsix 檔案,其中包含已封裝的擴充功能。

設定發行者

  1. 移至 Visual Studio Marketplace 發佈入口網站
  2. 如果您沒有發行者,請登入並建立發行者。
  3. 選擇一個唯一的發行者識別碼(此識別碼將用於您的內容清單檔案)。
  4. vss-extension.json 更新為使用您的發行者名稱,而非「fabrikam」。

上傳您的延伸模組

  1. 在發佈入口網站中,選取 [上傳新的擴充功能]。
  2. 選擇您的 .vsix 檔案並加以上傳。
  3. 與 Azure DevOps 組織共用延伸模組。

或者,使用命令列:

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

提示

使用 --rev-version 在更新現有的擴充功能時自動遞增版本號碼。

步驟 7:安裝及測試您的小工具

若要測試,請將您的小工具新增至控制面板:

  1. 移至您的 Azure DevOps 專案: https://dev.azure.com/{Your_Organization}/{Your_Project}
  2. 移至 [總覽]>[儀表板]
  3. 選擇新增小工具
  4. 在目錄中尋找您的小工具,然後選取 [ 新增]。

您的「Hello World」 小工具會出現在儀錶板上,並顯示您設定的文字。

下一個步驟:繼續進行 第 2 部分 ,以瞭解如何將 Azure DevOps REST API 整合到您的小工具中。

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

擴充您的小工具,以使用 REST API 與 Azure DevOps 資料互動。 此範例示範如何擷取查詢資訊,並在小工具中動態顯示它。

在此部分中,使用 工作項目追蹤 REST API 來擷取現有查詢的相關信息,並在 「Hello World」 文字下方顯示查詢詳細數據。

概觀儀錶板的螢幕快照,其中包含使用 REST API for WorkItemTracking 的範例小工具。

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

更新 Widget HTML 結構

請對 hello-world2.html 進行下列變更:

  1. 新增查詢數據的容器:包含可顯示查詢資訊的新 <div> 元素。
  2. 更新小工具識別碼:將小工具名稱 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"
    ]
}

這很重要

範圍限制:不支持在發佈后新增或變更範圍。 如果您已經發佈擴充功能,您必須先從 Marketplace 中移除它。 移至 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 API 用戶端的實例
getQuery() 擷取封裝在 Promise 中的查詢資訊
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:更新擴充功能指令清單

若要在 Widget 目錄中顯示它,請將新的小工具新增至延伸模組的清單檔。

新增第二個 Widget 貢獻

更新 vss-extension.json 以包含已啟用 REST API 的 Widget。 將此貢獻新增至 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:封裝、發佈和共用

封裝、發佈及共用您的延伸模組。 如果您已經發行擴充功能,您可以重新封裝並直接在 Marketplace 中更新。

步驟 7:測試您的 REST API 元件

若要檢視運作中的 REST API 整合,請將新的小工具新增至儀錶板:

  1. 移至您的 Azure DevOps 專案: https://dev.azure.com/{Your_Organization}/{Your_Project}
  2. 選取 概觀>。
  3. 選擇新增小工具
  4. 尋找 “Hello World Widget 2 (含 API)”,然後選取 [ 新增]。

增強的 Widget 會顯示來自 Azure DevOps 專案的 「Hello World」 文字和實時查詢資訊。

後續步驟:繼續進行 第 3 部分 ,以新增組態選項,讓使用者自定義要顯示的查詢。

第3部分:設定 Hello World

在第 2 部分 的基礎上,加入用戶設定功能到您的小工具。 建立組態介面,讓使用者使用即時預覽功能來選取要顯示的查詢,而不是硬式編碼查詢路徑。

此部分示範如何建立可設定的小工具,讓使用者可在設定期間提供即時意見反應時自定義其特定需求。

[概觀儀錶板] 小工具依據變更的實時預覽螢幕截圖。

步驟 1:建立組態檔

小工具組態與小工具本身共用許多相似之處,兩者都使用相同的SDK、HTML 結構和 JavaScript 模式,但在延伸模塊架構中有不同的用途。

設定專案結構

若要支援小工具組態,請建立兩個新的檔案:

  1. 複製 hello-world2.html 並將其重新命名為 hello-world3.html,您的可設定小工具。
  2. 建立名為 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中,進行下列變更:

  1. 更新小工具識別碼:從 HelloWorldWidget2 變更為 HelloWorldWidget3
  2. 新增重載函式:實作 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();
}

Widget 生命周期差異

功能 目標 使用方針
load() 初始元件渲染和一次性設定 大量作業、資源初始化
reload() 使用新的組態更新小工具 輕量型更新,數據重新整理

提示

效能優化:使用 load() 處理只需要執行一次的昂貴操作,並在設定變更時使用 reload() 進行快速更新。

(選擇性)新增燈箱以取得詳細資訊

儀錶板元件的空間有限,因此很難顯示詳細資訊。 燈箱提供優雅的解決方案,透過在模態覆蓋層中顯示詳細數據,而不需離開儀表板。

為什麼要在小工具中使用燈箱?

優點 描述
空間效率 提供詳細檢視時保持小工具精簡
使用者體驗 在顯示更多資訊時保持儀錶板的上下文
漸進式洩漏 在小工具中顯示摘要數據,詳細資訊可按需取得。
回應式設計 適應不同的螢幕大小和小工具組態

實作可點選的元素

更新查詢資料的呈現,以包含可點選的元素來觸發 lightbox。

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

建立燈箱功能

將此 Lightbox 實作新增至您的 Widget 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">&times;</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');
        }
    });
}

新增 Lightbox 樣式

在小工具 HTML <head> 區段中包含 Lightbox 的 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>

增強式小工具實作

具有 Lightbox 功能的完整強化組件:

<!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 的內建輔助功能功能進行測試。

這很重要

效能:Lightbox 應該會快速載入。 只有在 lightbox 開啟時,才考慮延遲載入詳細數據,而不是預先擷取所有數據。

步驟 5:設定擴充功能指令清單

在擴充功能 manifest 中登錄可配置的小工具及其配置介面。

新增小工具與組態貢獻

更新 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:套件、發佈和共用。 針對現有的擴充功能,請直接在 Marketplace 中重新封裝和更新。

步驟 7:測試可設定的小工具

藉由新增和設定您的小工具,體驗完整的設定工作流程。

將小工具新增至儀錶板

  1. 移至 https://dev.azure.com/{Your_Organization}/{Your_Project}
  2. 移至 [總覽]>[儀表板]
  3. 選擇新增小工具
  4. 尋找 「Hello World Widget 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 儀錶板小工具,其中包含即時預覽功能和使用者自定義選項。