次の方法で共有


ダッシュボード ウィジェットの追加

Azure DevOps Services |Azure DevOps Server |Azure DevOps Server 2022

ウィジェットは、拡張機能フレームワークコントリビューションとして実装されます。 1 つの拡張機能に複数のウィジェットコントリビューションを含めることができます。 この記事では、1 つ以上のウィジェットを提供する拡張機能を作成する方法について説明します。

ヒント

テーマ設定や VSS からの移行など、最新の拡張機能開発ガイダンスについて説明します。SDK については、 Azure DevOps Extension SDK 開発者ポータルを参照してください。

ヒント

新しい Azure DevOps 拡張機能を開始する場合は、まず、保守されているこれらのサンプル コレクションを試してください。現在の製品ビルドで動作し、最新のシナリオ (プル要求ページにタブを追加するなど) について説明します。

サンプルが組織で機能しない場合は、個人用またはテスト組織にインストールし、拡張機能マニフェストのターゲット 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

チュートリアルの概要

このチュートリアルでは、次の 3 つの段階的な例を使用してウィジェットの開発について説明します。

パーツ フォーカス 学習内容
パート 1: Hello World 基本的なウィジェットの作成 テキストを表示するウィジェットを作成する
パート 2: REST API の統合 Azure DevOps API 呼び出し データをフェッチして表示するための REST API 機能を追加する
パート 3: ウィジェットの構成 ユーザーによるカスタマイズ ウィジェットの構成オプションを実装する

ヒント

作業例に直接進む場合は、付属のサンプル (前のメモを参照) に、パッケージ化して発行できる一連のウィジェットが表示されます。

開始する前に、 提供されている基本的なウィジェット のスタイル と構造ガイダンスを確認してください。

パート 1: Hello World

JavaScript を使用して "Hello World" を表示する基本的なウィジェットを作成します。 この基礎は、ウィジェット開発の中心概念を示しています。

サンプル ウィジェットを含む [概要] ダッシュボードのスクリーンショット。

クライアント 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 ページを参照してください。

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 ヘッド要素はフレームワークによって無視されます。

ウィジェット 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() 読み込みが正常に完了したことをフレームワークに通知します

Von Bedeutung

VSS.register()のウィジェット名は、拡張機能マニフェストのidと一致する必要があります (手順 5)。

拡張機能イメージを追加する

拡張機能に必要なイメージを作成します。

  • 拡張ロゴ: logo.png フォルダー内の img という名前の 98 x 98 ピクセルの画像
  • ウィジェット カタログ アイコン: CatalogIcon.png フォルダー内の img という名前の 98 x 98 ピクセルの画像
  • ウィジェット プレビュー: preview.png フォルダー内の img という名前の 330 x 160 ピクセルの画像

これらのイメージは、ユーザーが使用可能な拡張機能を参照すると、Marketplace およびウィジェット カタログに表示されます。

拡張機能マニフェストを作成する

プロジェクトのルート ディレクトリに 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
        }
    ]
}

Von Bedeutung

"publisher": "fabrikam"を実際の発行元名に置き換えます。 パブリッシャーを作成する方法について説明します。

必須のマニフェスト プロパティ

セクション 目的
基本情報 拡張機能の名前、バージョン、説明、発行元
アイコン 拡張機能のビジュアル アセットへのパス
貢献 ID、型、およびプロパティを含むウィジェット定義
ファイル 拡張パッケージに含めるすべてのファイル

マニフェストの完全なドキュメントについては、「 拡張機能マニフェストリファレンス」を参照してください

拡張機能をパッケージ化して発行する

拡張機能をパッケージ化し、Visual Studio Marketplace に発行します。

パッケージ 化ツールをインストールする

npm i -g tfx-cli

拡張機能パッケージを作成する

プロジェクト ディレクトリから、次のコマンドを実行します。

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

このアクションにより、パッケージ化された拡張機能を含む .vsix ファイルが作成されます。

パブリッシャーを設定する

  1. Visual Studio Marketplace 発行ポータルに移動します
  2. パブリッシャーがない場合は、サインインしてパブリッシャーを作成します。
  3. 一意の発行元識別子 (マニフェスト ファイルで使用) を選択します。
  4. "fabrikam" ではなくパブリッシャー名を使用するように vss-extension.json を更新します。

拡張機能をアップロードする

  1. 発行ポータルで、[ 新しい拡張機能のアップロード] を選択します。
  2. .vsix ファイルを選択してアップロードします。
  3. 拡張機能を Azure DevOps 組織と共有します。

または、コマンド ラインを使用します。

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

ヒント

--rev-versionを使用して、既存の拡張機能を更新するときにバージョン番号を自動的にインクリメントします。

ウィジェットをインストールしてテストする

テストするには、ウィジェットをダッシュボードに追加します。

  1. Azure DevOps プロジェクトに移動します: https://dev.azure.com/{Your_Organization}/{Your_Project}
  2. [Overview]>[ダッシュボード] に移動します。
  3. ウィジェットを追加を選択します。
  4. カタログでウィジェットを見つけて、[ 追加] を選択します。

"Hello World" ウィジェットがダッシュボードに表示され、構成したテキストが表示されます。

次の手順: パート 2 に進み、Azure DevOps REST API をウィジェットに統合する方法について説明します。

パート 2: Azure DevOps REST API を使用したHello World

REST API を使用して、ウィジェットを拡張して Azure DevOps データと対話します。 この例では、クエリ情報をフェッチし、ウィジェットに動的に表示する方法を示します。

このパートでは、 作業項目追跡 REST API を使用して既存のクエリに関する情報を取得し、クエリの詳細を "Hello World" テキストの下に表示します。

WorkItemTracking 用 REST API を使用したサンプル ウィジェットを含む [概要] ダッシュボードのスクリーンショット。

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に次の変更を加えます。

  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>

API のアクセス許可を構成する

REST API 呼び出しを行う前に、拡張機能マニフェストで必要なアクセス許可を構成します。

作業範囲を追加する

vso.work スコープは、作業項目とクエリへの読み取り専用アクセスを許可します。 このスコープを vss-extension.jsonに追加します。

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

マニフェストの例

他のプロパティを含む完全なマニフェストの場合は、次のように構成します。

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

Von Bedeutung

スコープの制限事項: 発行後のスコープの追加または変更はサポートされていません。 拡張機能を既に公開している場合は、最初に Marketplace から拡張機能を削除する必要があります。 Visual Studio Marketplace 発行ポータルに移動し、拡張機能を見つけて、[削除] を選択します

REST API 呼び出しを実装する

Azure DevOps は、SDK を介して JavaScript REST クライアント ライブラリを提供します。 これらのライブラリは、AJAX 呼び出しをラップし、API 応答を使用可能なオブジェクトにマップします。

ウィジェット JavaScript を更新する

作業項目追跡 REST クライアントを含めるために、VSS.requireにあるhello-world2.html呼び出しを置き換えてください。

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() 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" テキストの下に 3 つの重要な情報を表示します。

完全な例

最終的な 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>

マニフェストを更新する

ウィジェット カタログで使用できるようにするには、新しいウィジェットを拡張機能マニフェストに追加します。

ウィジェットのコントリビューションを追加する

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 イメージ (330 x 160 ピクセル) を作成し、 img フォルダーに配置して、カタログ内のウィジェットの外観をユーザーに表示します。

パッケージ化と発行

拡張をパッケージ化し、公開して共有しましょう。 拡張機能を既に公開している場合は、Marketplace で直接再パッケージ化して更新できます。

REST API ウィジェットをテストする

REST API の統合を実際に表示するには、新しいウィジェットをダッシュボードに追加します。

  1. Azure DevOps プロジェクトに移動します: https://dev.azure.com/{Your_Organization}/{Your_Project}
  2. [概要]>[ダッシュボード] を選択します。
  3. ウィジェットを追加を選択します。
  4. "Hello World Widget 2 (WITH API)" を見つけて、[ 追加] を選択します。

強化されたウィジェットには、Azure DevOps プロジェクトの "Hello World" テキストとライブ クエリ情報の両方が表示されます。

次の手順: パート 3 に進み、表示するクエリをユーザーがカスタマイズできる構成オプションを追加します。

パート 3: 構成可能なウィジェット

パート 2 を基にして、ウィジェットにユーザー構成機能を追加します。 クエリ パスをハードコーディングする代わりに、ライブ プレビュー機能を使用して、表示するクエリをユーザーが選択できる構成インターフェイスを作成します。

このパートでは、構成中にリアルタイムのフィードバックを提供しながら、ユーザーが特定のニーズに合わせてカスタマイズできる構成可能なウィジェットを作成する方法を示します。

変更に基づくウィジェットの概要ダッシュボードライブ プレビューのスクリーンショット。

構成ファイルを作成する

ウィジェットの構成は、ウィジェット自体と多くの類似点を共有します。どちらも同じ SDK、HTML 構造、JavaScript パターンを使用しますが、拡張機能フレームワーク内では異なる目的を果たします。

プロジェクト構造

ウィジェットの構成をサポートするには、次の 2 つの新しいファイルを作成します。

  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>

構成ロジックを実装する

構成 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 経由でこれらの設定にアクセスし、それに応じて逆シリアル化する必要があります。

ライブ プレビューを有効にする

ライブ プレビューを使用すると、ユーザーは構成設定を変更するときにウィジェットの変更をすぐに確認でき、保存する前にすぐにフィードバックを提供できます。

変更通知

ライブ プレビューを有効にするには、 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>

Von Bedeutung

[保存を有効にする] ボタン: フレームワークで [保存] ボタンを有効にするには、少なくとも 1 つの構成変更通知が必要です。 変更イベント ハンドラーにより、ユーザーがオプションを選択したときにこのアクションが確実に実行されます。

ウィジェットを構成可能にする

ハードコーディングされた値の代わりに構成データを使用するように、パート 2 からウィジェットを変換します。 この手順では、 IConfigurableWidget コントラクトを実装する必要があります。

ウィジェットの登録を更新する

hello-world3.htmlで、次の変更を行います。

  1. ウィジェット ID の更新: 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();
}

ウィジェットのライフサイクル

機能 目的 使用ガイドライン
load() ウィジェットの初期レンダリングと 1 回限りセットアップ 大量の操作、リソースの初期化
reload() 新しい構成でウィジェットを更新する 軽量更新、データ更新

ヒント

パフォーマンスの最適化: load() は、1 回だけ実行する必要があるコストの高い操作に使用し、構成が変更されたときにすばやく更新するために 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">&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');
        }
    });
}

ウィジェットの 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 の組み込みのアクセシビリティ機能を使用してテストします。

Von Bedeutung

パフォーマンス: ライトボックスはすぐに読み込まれます。 ライトボックスが開いたときにのみ詳細データを遅延ロードすることを検討し、すべてを先に取得しないようにしてください。

マニフェストを構成する

構成可能なウィジェットの両方を登録する

ウィジェットと設定の貢献

vss-extension.jsonを更新して、2 つの新しい投稿を含めます。

{
    "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: パッケージ化、発行、共有に従います。 既存の拡張機能の場合は、Marketplace で直接再パッケージ化して更新します。

構成可能なウィジェットをテストする

ウィジェットを追加して構成することで、完全な構成ワークフローを体験できます。

ダッシュボードに追加する

  1. https://dev.azure.com/{Your_Organization}/{Your_Project} にアクセスします。
  2. [Overview]>[ダッシュボード] に移動します。
  3. ウィジェットを追加を選択します。
  4. [Hello World Widget 3 (with config)]\(Hello World ウィジェット 3 (構成あり)\) を見つけて、[ 追加] を選択します。

ウィジェットにはセットアップが必要なため、構成プロンプトが表示されます。

カタログのサンプル ウィジェットを含む [概要] ダッシュボードのスクリーンショット。

ウィジェットを構成する

次のいずれかの方法で構成にアクセスします。

  • ウィジェット メニュー: ウィジェットの上にマウス ポインターを合わせ、省略記号 (⋯) を選択し、構成
  • ダッシュボード編集モード: ダッシュボード[編集] を選択し、ウィジェットの [構成] ボタンを選択します

構成パネルが開き、中央にライブ プレビューが表示されます。 ドロップダウンからクエリを選択して即時の更新を表示し、[ 保存] を選択して変更を適用します。

詳細オプションの追加

カスタム名やサイズなど、より組み込みの構成機能を使用してウィジェットを拡張します。

名前とサイズのオプション

Azure DevOps には、すぐに使用できる 2 つの構成可能な機能が用意されています。

特徴 マニフェスト プロパティ 目的
カスタム名 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 ダッシュボード ウィジェットを作成しました。