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

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

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

この記事は 3 つの部分に分かれています。各部分は前の上に構築されています。単純なウィジェットから始まり、包括的なウィジェットで終わるのです。

ヒント

Azure DevOps 拡張機能 SDK を使用した拡張機能開発に関する最新のドキュメントを確認してください。

前提条件

  • ナレッジ: ウィジェットの開発には、JavaScript、HTML、CSS に関する知識が必要です。
  • Azure DevOps の組織
  • テキスト エディター。 多くのチュートリアルでは、 Visual Studio Code を使用します。
  • nodeの最も新しいバージョン。
  • 拡張機能をパッケージ化するための Azure DevOps 用クロスプラットフォーム CLI (tfx-cli )。
    • tfx-cli は を使用して npmインストールできます。これは、 を実行してNode.jsのコンポーネントです。 npm i -g tfx-cli
  • プロジェクトのホーム ディレクトリ。 このディレクトリは、チュートリアル全体で と home 呼ばれます。

拡張ファイルの構造:

|--- README.md
|--- sdk    
    |--- node_modules           
    |--- scripts
        |--- VSS.SDK.min.js       
|--- img                        
    |--- logo.png                           
|--- scripts                        
|--- hello-world.html               // html page to be used for your widget  
|--- vss-extension.json             // extension's manifest

このチュートリアルの内容

  1. パート 1: 単純な "Hello World" メッセージを出力する新しいウィジェットを作成する方法について説明します。
  2. パート 2: Azure DevOps REST API への呼び出しを追加することで、最初の部分に基づいています。
  3. パート 3: ウィジェットに構成を追加する方法について説明します。

Note

急いですぐにコードを入手したい場合は、 サンプルをダウンロードできます。 ダウンロードしたら、フォルダーに widgets 移動し、 手順 6手順 7 に直接従って、さまざまな複雑さの 3 つのサンプル ウィジェットを含むサンプル拡張機能を公開します。

すぐに使えるウィジェットの 基本的なスタイル ウィジェットの構造に関するいくつかのガイダンスを提供します。

パート 1: Hello World

パート 1 では、JavaScript を使用して "Hello World" を出力するウィジェットを示します。

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

手順 1: クライアント SDK を取得する - VSS.SDK.min.js

コア SDK スクリプト は、 VSS.SDK.min.jsWeb 拡張機能がホストの Azure DevOps フレームと通信できるようにします。 スクリプトは、初期化、拡張機能の読み込み通知、または現在のページに関するコンテキストの取得などの操作を実行します。 クライアント SDK VSS.SDK.min.js ファイルを取得し、Web アプリに追加します。 フォルダーに home/sdk/scripts 配置します。

SDK を取得するには、'npm install' コマンドを使用します。

npm install vss-web-extension-sdk

詳細については、 Client SDK GitHub のページを参照してください。

手順 2: HTML ページを設定する - hello-world.html

HTML ページは、レイアウトを一緒に保持し、CSS と JavaScript への参照を含む接着です。 このファイルには何でも名前を付けることができます。 hello-worldへのすべての参照を、使用する名前で更新します。

ウィジェットは HTML ベースで、 iframe でホストされています。 hello-world.htmlに次の HTML を追加します。 VSS.SDK.min.js ファイルへの必須参照を追加し、h2要素を含めます。この要素は、今後の手順で Hello World という文字列で更新されます。

<!DOCTYPE html>
<html>
    <head>          
        <script src="sdk/scripts/VSS.SDK.min.js"></script>              
    </head>
    <body>
        <div class="widget">
            <h2 class="title"></h2>
        </div>
    </body>
</html>

HTML ファイルを使用していても、スクリプトやリンク以外の HTML ヘッド要素のほとんどはフレームワークによって無視されます。

手順 3: JavaScript を更新する

JavaScript を使用して、ウィジェット内のコンテンツをレンダリングします。 この記事では、すべての JavaScript コードを HTML ファイルの 要素内 &lt;script&gt; でラップします。 このコードを別の JavaScript ファイルに含め、HTML ファイルで参照することもできます。 コードはコンテンツをレンダリングします。 この JavaScript コードでは、VSS SDK も初期化され、ウィジェットのコードがウィジェット名にマップされ、ウィジェットの成功または失敗が拡張機能フレームワークに通知されます。 この場合、次のコードはウィジェットに "Hello World" を出力します。 この script 要素を head HTML の の に追加します。

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

    VSS.require("TFS/Dashboards/WidgetHelpers", function (WidgetHelpers) {
        WidgetHelpers.IncludeWidgetStyles();
        VSS.register("HelloWorldWidget", function () {                
            return {
                load: function (widgetSettings) {
                    var $title = $('h2.title');
                    $title.text('Hello World');

                    return WidgetHelpers.WidgetStatusHelper.Success();
                }
            };
        });
        VSS.notifyLoadSucceeded();
    });
</script>

  • VSS.init は、ウィジェットをホストしている iframe とホスト フレームの間のハンドシェイクを初期化します。
  • 読み込みが完了したときにウィジェットがホストに明示的に通知できるように、 explicitNotifyLoaded: true を渡します。 このコントロールを使用すると、依存モジュールが読み込まれたことを確認した後、読み込みの完了を通知できます。 ウィジェットが HTML 要素 (body、div など) に Azure DevOps コア スタイルを使用できるように、 usePlatformStyles: true を渡します。 ウィジェットでこれらのスタイルを使用しない場合は、 を渡 usePlatformStyles: falseすことができます。
  • VSS.require は、必要な VSS スクリプト ライブラリを読み込むのに使用されます。 このメソッドを呼び出すと、 JQuery や JQueryUI などの一般的なライブラリが自動的に読み込 まれます。 ここでは、ウィジェット フレームワークにウィジェットの状態を伝えるために使用される WidgetHelpers ライブラリに依存しています。 そのため、対応するモジュール名 TFS/Dashboards/WidgetHelpers とコールバックを に VSS.require渡します。 コールバックは、モジュールが読み込まれた後に呼び出されます。 コールバックには、ウィジェットに必要な残りの JavaScript コードがあります。 コールバックの最後に を呼び出 VSS.notifyLoadSucceeded して、読み込みの完了を通知します。
  • WidgetHelpers.IncludeWidgetStyles には、作業を開始するための 基本的な css を含むスタイルシートが含まれています。 これらのスタイルを使用するには、クラス widgetを使用して HTML 要素内でコンテンツをラップします。
  • VSS.register は、拡張機能のさまざまなコントリビューション間でウィジェットを一意に識別する JavaScript の関数をマップするために使用されます。 この名前は、手順 5 で説明されているように、コントリビューションを識別する と一致しているid必要があります。 ウィジェットの場合、 に VSS.register 渡される関数はコントラクトを満たす IWidget オブジェクトを返す必要があります。たとえば、返されるオブジェクトには、ウィジェットをレンダリングするコア ロジックを持つ別の関数である値を持つ load プロパティが必要です。 ここでは、 h2 要素のテキストを "Hello World" に更新します。これは、ウィジェット フレームワークがウィジェットをインスタンス化するときに呼び出されるこの関数です。 From WidgetHelpers を WidgetStatusHelper 使用して、 を WidgetStatus 成功として返します。

警告

ウィジェットの登録に使用される名前がマニフェスト内のコントリビューションの ID と一致しない場合、ウィジェットは予期せず機能します。

  • vss-extension.json は常にフォルダーのルートにある必要があります (このガイドでは、 HelloWorld)。 他のすべてのファイルについては、フォルダー内の任意の構造に配置できます。HTML ファイルと vss-extension.json マニフェストで参照を適切に更新してください。

手順 4: 拡張機能のロゴを更新する: logo.png

ユーザーが拡張機能をインストールすると、Marketplace とウィジェット カタログにロゴが表示されます。

98 px x 98 px のカタログ アイコンが必要です。 イメージを選択し、 という名前を付け logo.png、 フォルダーに img 配置します。

これらのイメージには、次の手順の拡張機能マニフェストが使用する名前で更新されている限り、必要に応じて名前を付けることができます。

手順 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
        }
    ]
}

必要な属性の詳細については、 Extension マニフェスト リファレンスを参照してください。

Note

publisherを発行元名に変更します。 パブリッシャーを作成するには、「 Package/Publish/Installを参照してください。

アイコン

アイコン スタンザは、マニフェスト内の拡張機能のアイコンへのパスを指定します。

貢献

各コントリビューション エントリはプロパティを定義 します

  • 投稿を識別する ID 。 この ID は拡張機能内で一意である必要があります。 この ID は、 手順 3 でウィジェットを登録するために使用した名前と一致している必要があります。
  • コントリビューションの 種類 。 すべてのウィジェットの場合、型は である ms.vss-dashboards-web.widget必要があります。
  • コントリビューションが貢献している ターゲット の配列。 すべてのウィジェットのターゲットは である [ms.vss-dashboards-web.widget-catalog]必要があります。
  • プロパティは、コントリビューション型のプロパティを含むオブジェクトです。 ウィジェットの場合は、次のプロパティが必須です。
プロパティ 内容
name ウィジェット カタログに表示するウィジェットの名前。
description ウィジェット カタログに表示するウィジェットの説明。
catalogIconUrl ウィジェット カタログに表示する 手順 4 で追加したカタログ アイコンの相対パス。 画像は 98 px x 98 px にする必要があります。 別のフォルダー構造または別のファイル名を使用した場合は、ここで適切な相対パスを指定します。
previewImageUrl ウィジェット カタログに表示する Step 4 で追加したプレビュー イメージの相対パス。 画像は 330 px x 160 px にする必要があります。 別のフォルダー構造または別のファイル名を使用した場合は、ここで適切な相対パスを指定します。
uri 手順 1 で追加した HTML ファイルの相対パス。 別のフォルダー構造または別のファイル名を使用した場合は、ここで適切な相対パスを指定します。
supportedSizes ウィジェットでサポートされているサイズの配列。 ウィジェットで複数のサイズがサポートされている場合、配列の最初のサイズはウィジェットの既定のサイズです。 widget sizeは、ダッシュボード グリッド内のウィジェットによって占有される行と列に対して指定されます。 1 つの行/列が 160 px に対応します。 1x1 より大きいディメンションでは、ウィジェット間の余白を表す 10 px が追加されます。 たとえば、3x2 ウィジェットは幅が 160*3+10*2 広く 160*2+10*1 高いです。 サポートされる最大サイズは です 4x4
supportedScopes 現時点では、チーム ダッシュボードのみがサポートされています。 値は である必要があります project_team。 今後の更新には、ダッシュボード スコープのオプションが追加される可能性があります。

ファイル

ファイル スタンザには、パッケージに含めるファイル (HTML ページ、スクリプト、SDK スクリプト、ロゴ) が示されます。 true URL アドレス指定可能である必要のない他のファイルを含めない限り、 を に設定addressableします。

Note

拡張機能マニフェスト ファイルのプロパティや実行内容など、拡張機能マニフェスト ファイルの詳細については、拡張機能マニフェスト リファレンスを参照してください

手順 6: パッケージ化、発行、共有

作成した拡張機能を Marketplace に追加する次の手順は、すべてのファイルを一緒にパッケージ化することです。 すべての拡張機能は、VSIX 2.0 互換の .vsix ファイルとしてパッケージ化されています。Microsoft は、拡張機能をパッケージ化するためのクロスプラットフォーム コマンド ライン インターフェイス (CLI) を提供します。

パッケージ化ツールを取得する

コマンド ラインから、Node.jsのコンポーネントである を使用してnpm、Azure DevOps 用クロスプラットフォーム CLI (tfx-cli) をインストールまたは更新できます。

npm i -g tfx-cli

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

tfx-cli を使用すると、拡張子を .vsix ファイルに簡単にパッケージ化できます。 拡張機能のホーム ディレクトリに移動し、次のコマンドを実行します。

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

Note

拡張機能/統合バージョンは、更新ごとにインクリメントする必要があります。
既存の拡張機能を更新する場合は、マニフェストのバージョンを更新するか、 --rev-version コマンド ライン スイッチを渡します。 これにより、拡張機能の パッチ バージョン番号がインクリメントされ、新しいバージョンがマニフェストに保存されます。

.vsix ファイルに拡張機能をパッケージ化したら、拡張機能を Marketplace に発行する準備が整います。

拡張機能の発行元を作成する

Microsoft の拡張機能を含むすべての拡張機能は、発行元によって提供されていると識別されます。 既存のパブリッシャーのメンバーでない場合は、作成します。

  1. Visual Studio Marketplace 発行ポータルにサインインする
  2. 既存のパブリッシャーのメンバーでない場合は、パブリッシャーを作成する必要があります。 パブリッシャーが既にある場合は、[関連サイト関連サイト] までスクロールして選択
    • 発行元の識別子を指定します。次に例を示します。 mycompany-myteam
      • 識別子は、拡張機能のマニフェスト ファイル内の publisher 属性の値として使用されます。
    • 次のように、発行元の表示名を指定します。 My Team
  3. Marketplace パブリッシャー契約を確認し作成を選択します。

これで、発行元が定義されました。 今後のリリースでは、発行元の拡張機能を表示および管理するためのアクセス許可を付与できます。

共通の発行元の下で拡張機能を公開すると、チームや組織のプロセスが簡略化され、より安全なアプローチが提供されます。 この方法では、1 つの資格情報セットを複数のユーザーに配布する必要がなくなり、セキュリティが強化され、

サンプルの vss-extension.json マニフェスト ファイルを更新して、ダミーのパブリッシャー ID fabrikam を発行元 ID に置き換えます。

拡張機能を発行して共有する

これで、拡張機能を Marketplace にアップロードできるようになりました。

新しい拡張機能のアップロードを選択、パッケージ化された .vsix ファイルに移動し、アップロードを選択します。

また、1 つの手順で拡張機能をパッケージ化して発行する代わりに tfx extension create コマンドをtfx extension publish使用して、コマンド ラインを使用して拡張機能をアップロードすることもできます。 必要に応じて を使用 --share-with して、公開後に拡張機能を 1 つ以上のアカウントと共有できます。 個人用アクセス トークンも必要です。

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

手順 7: カタログからウィジェットを追加する

  1. プロジェクトにサインイン http://dev.azure.com/{Your_Organization}/{Your_Project}

  2. Overview>Dashboards を選択します。

  3. [Add a widget] を選択します。

  4. ウィジェットを強調表示し、[ 追加] を選択します。

    ウィジェットがダッシュボードに表示されます。

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

ウィジェットは、Azure DevOps 内の 任意の REST API を 呼び出して、Azure DevOps リソースと対話できます。 次の例では、WorkItemTracking 用 REST API を使用して、既存のクエリに関する情報を取得し、ウィジェットの [Hello World] テキストの下にクエリ情報を表示します。

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

手順 1: HTML ファイルを追加する

前の例のファイル hello-world.html をコピーし、コピーの名前を に hello-world2.html変更します。 フォルダーは次の例のようになります。

|--- README.md |--- node_modules
|--- SDK
|--- スクリプト |--- VSS。|--- img |--- logo.png |--- スクリプトのSDK.min.js
ウィジェットに使用する |--- hello-world.html // html ページ |--- hello-world2.html // 拡張子のマニフェストの名前が変更されたhello-world.htmlのコピー |--- vss-extension.json //

クエリ情報を保持するには、h2の下に新しいdiv要素を追加します。 を呼び出VSS.registerす行で、ウィジェットの名前を から HelloWorldWidgetHelloWorldWidget2更新します。 このアクションにより、フレームワークは拡張機能内のウィジェットを一意に識別できます。

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

            VSS.require("TFS/Dashboards/WidgetHelpers", function (WidgetHelpers) {
                WidgetHelpers.IncludeWidgetStyles();
                VSS.register("HelloWorldWidget2", function () {
                    return {
                        load: function (widgetSettings) {
                            var $title = $('h2.title');
                            $title.text('Hello World');

                            return WidgetHelpers.WidgetStatusHelper.Success();
                        }
                    }
                });
                VSS.notifyLoadSucceeded();
            });
        </script>
    </head>
    <body>
        <div class="widget">
            <h2 class="title"></h2>
            <div id="query-info-container"></div>
        </div>
    </body>
</html>

手順 2: Azure DevOps リソースにアクセスする

Azure DevOps リソースへのアクセスを有効にするには、拡張機能マニフェストで スコープ を指定する必要があります。 スコープを vso.work マニフェストに追加します。
このスコープは、ウィジェットがクエリと作業項目への読み取り専用アクセス権を必要とすることを示します。 使用可能なすべてのスコープについては、 こちらを参照してください。 拡張機能マニフェストの末尾に次のコードを追加します。

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

その他のプロパティを含めるには、それらを明示的に一覧表示する必要があります。次に例を示します。

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

警告

拡張機能の発行後にスコープを追加または変更することは現在サポートされていません。 拡張機能を既にアップロードしている場合は、Marketplace から削除します。 Visual Studio Marketplace 発行ポータルに移動し拡張機能を右選択し、Remove を選択します。

手順 3: REST API 呼び出しを行う

AZURE DevOps で REST API 呼び出しを行うために SDK を介してアクセスできるクライアント側ライブラリは多数あります。 これらのライブラリは REST クライアントと呼ばれ、使用可能なすべてのサーバー側エンドポイントの Ajax 呼び出しに関する JavaScript ラッパーです。 Ajax 呼び出しを自分で記述する代わりに、これらのクライアントによって提供されるメソッドを使用できます。 これらのメソッドは、API 応答をコードで使用できるオブジェクトにマップします。

この手順では、呼び出しを VSS.require 更新して読み込みます AzureDevOps/WorkItemTracking/RestClient。これにより、WorkItemTracking REST クライアントが提供されます。 この REST クライアントを使用して、 フォルダー Shared Queriesの下にある というFeedbackクエリに関する情報を取得できます。

に渡す関数内に VSS.register、現在のプロジェクト ID を保持する変数を作成します。 クエリをフェッチするには、この変数が必要です。 また、REST クライアントを使用する新しいメソッド getQueryInfo も作成します。 その後、load メソッドから呼び出されるこのメソッド。

メソッド getClient は、必要な REST クライアントのインスタンスを提供します。 メソッド getQuery は、promise でラップされたクエリを返します。 更新された VSS.require 内容は次のようになります。

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

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

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

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

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

から Failure メソッド WidgetStatusHelperを使用していることに注意してください。 これにより、エラーが発生したことをウィジェット フレームワークに示し、すべてのウィジェットに提供される標準的なエラー エクスペリエンスを利用できます。

Shared Queries フォルダーの下に Feedback クエリがない場合は、コード内のShared Queries\Feedbackを、プロジェクトに存在するクエリのパスに置き換えます。

手順 4: 応答を表示する

最後の手順では、ウィジェット内にクエリ情報をレンダリングします。 関数は getQuery 、promise 内の型 Contracts.QueryHierarchyItem のオブジェクトを返します。 この例では、クエリ ID、クエリ名、クエリ作成者の名前を "Hello World" テキストの下に表示します。 コメント `` を、次のコードに置き換えます。

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

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

最終的な hello-world2.html は、次の例のようになります。

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

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

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

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

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

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

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

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

手順 5: 拡張機能マニフェストを更新する

この手順では、拡張機能マニフェストを更新して、2 番目のウィジェットのエントリを含めます。 プロパティの 配列に新しいコントリビューションを contributions 追加し、新しいファイル hello-world2.html を files プロパティの配列に追加します。 2 番目のウィジェットには別のプレビュー イメージが必要です。 これに preview2.png 名前を付け、 フォルダーに img 配置します。

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

手順 6: パッケージ化、発行、共有

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

手順 7: カタログからウィジェットを追加する

次に、 のチーム ダッシュボードに https:\//dev.azure.com/{Your_Organization}/{Your_Project}移動します。 このページが既に開いている場合は、更新します。 Editにカーソルを合わせ、[追加選択。 ウィジェット カタログが開き、インストールしたウィジェットが見つかります。 ダッシュボードに追加するには、ウィジェットを選択し、 [追加] を選択します。

パート 3: Hello World を構成する

このガイドの パート 2 では、ハードコーディングされたクエリのクエリ情報を表示するウィジェットを作成する方法について説明しました。 このパートでは、ハードコーディングされたクエリではなく、使用するクエリを構成する機能を追加します。 構成モードの場合、ユーザーは変更に基づいてウィジェットのライブ プレビューを表示します。 ユーザーが [保存] を選択すると、これらの変更がダッシュボードのウィジェットに 保存されます

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

手順 1: HTML ファイルを追加する

ウィジェットとウィジェット構成の実装は、よく似ています。 どちらも、コントリビューションとして拡張フレームワークで実装されます。 どちらも同じ SDK ファイル を使用します VSS.SDK.min.js。 どちらも HTML、JavaScript、CSS に基づいています。

前の例のファイル html-world2.html をコピーし、コピーの名前を に hello-world3.html変更します。 という名前 configuration.htmlの別の HTML ファイルを追加します。 フォルダーは次の例のようになります。

|--- README.md
|--- sdk    
    |--- node_modules           
    |--- scripts
        |--- VSS.SDK.min.js       
|--- img                        
    |--- logo.png                           
|--- scripts          
|--- configuration.html                          
|--- hello-world.html               // html page to be used for your widget  
|--- hello-world2.html              // renamed copy of hello-world.html
|--- hello-world3.html              // renamed copy of hello-world2.html
|--- vss-extension.json             // extension's manifest

configuration.htmlに次の HTML を追加します。 基本的には、ファイルへの VSS.SDK.min.js 必須参照と、ドロップダウンの要素を select 追加して、プリセット リストからクエリを選択します。

    <!DOCTYPE html>
    <html xmlns="http://www.w3.org/1999/xhtml">
        <head>                          
            <script src="sdk/scripts/VSS.SDK.min.js"></script>              
        </head>
        <body>
            <div class="container">
                <fieldset>
                    <label class="label">Query: </label>
                    <select id="query-path-dropdown" style="margin-top:10px">
                        <option value="" selected disabled hidden>Please select a query</option>
                        <option value="Shared Queries/Feedback">Shared Queries/Feedback</option>
                        <option value="Shared Queries/My Bugs">Shared Queries/My Bugs</option>
                        <option value="Shared Queries/My Tasks">Shared Queries/My Tasks</option>                        
                    </select>
                </fieldset>             
            </div>
        </body>
    </html>

手順 2: JavaScript を構成する

このガイドのパート 1 の 手順 3 でウィジェットに対して行ったように、JavaScript を使用してウィジェット構成のコンテンツをレンダリングします。 この JavaScript コードは、コンテンツをレンダリングし、VSS SDK を初期化し、ウィジェット構成のコードを構成名にマップし、構成設定をフレームワークに渡します。 この場合、次のコードはウィジェットの構成を読み込みます。 ファイル configuration.html を開き、次の <script> 要素を <head>に開きます。

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

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

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

                    return WidgetHelpers.WidgetStatusHelper.Success();
                },
                onSave: function() {
                    var customSettings = {
                        data: JSON.stringify({
                                queryPath: $queryDropdown.val()
                            })
                    };
                    return WidgetHelpers.WidgetConfigurationSave.Valid(customSettings); 
                }
            }
        });
        VSS.notifyLoadSucceeded();
    });
</script>
  • VSS.initVSS.require、および VSS.register は、 Part 1 で説明されているように、ウィジェットに対して実行したのと同じ役割を果たします。 唯一の違いは、ウィジェット構成の場合、 に VSS.register 渡される関数はコントラクトを満たすオブジェクトを IWidgetConfiguration 返す必要があるということです。
  • loadコントラクトの プロパティには、IWidgetConfigurationその値として 関数が必要です。 この関数には、ウィジェット構成をレンダリングするための一連の手順があります。 ここでは、ドロップダウン要素の選択した値を既存の設定があれば更新します。 この関数は、フレームワークが をインスタンス化するときに呼び出されます。 widget configuration
  • onSaveコントラクトの プロパティには、IWidgetConfigurationその値として 関数が必要です。 この関数は、ユーザーが構成ウィンドウで [保存] を選択すると、フレームワークによって呼び出されます。 ユーザー入力を保存する準備ができたら、それを文字列にシリアル化し、 オブジェクトを custom settings 形成し、 を使用 WidgetConfigurationSave.Valid() してユーザー入力を保存します。

このガイドでは、JSON を使用して、ユーザー入力を文字列にシリアル化します。 ユーザー入力を文字列にシリアル化するその他の方法を選択できます。 WidgetSettings オブジェクトの customSettings プロパティを使用してウィジェットからアクセスできます。 ウィジェットは逆シリアル化する必要があります。これは、 手順 4 で説明します。

手順 3: JavaScript - ライブ プレビューを有効にする

ユーザーがドロップダウンからクエリを選択したときにライブ プレビューの更新を有効にするには、ボタンに変更イベント ハンドラーをアタッチします。 このハンドラーは、構成が変更されたことをフレームワークに通知します。 また、プレビューの customSettings 更新に使用する を渡します。 フレームワークに通知するには、 の notify メソッドを widgetConfigurationContext 呼び出す必要があります。 2 つのパラメーターを受け取ります。この例 WidgetHelpers.WidgetEvent.ConfigurationChangeでは、 イベントの名前と EventArgs 、ヘルパー メソッドの助けを借りて から customSettings 作成された イベントの WidgetEvent.Args オブジェクトです。

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

改訂: 保存 ボタンを有効にするために、構成の変更が少なくとも 1 回フレームワークに通知されるようにします。

最後に、 configuration.html は次の例のようになります。

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

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

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

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

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

手順 4: ウィジェットに再読み込みを実装する - JavaScript

ユーザーが選択したクエリ パスを格納するようにウィジェット構成を設定します。 ここで、前の例のハードコーディング Shared Queries/Feedback ではなく、この格納された構成を使用するようにウィジェット内のコードを更新する必要があります。

ファイルhello-world3.htmlを開き、 を呼び出VSS.registerす行の から にHelloWorldWidget3ウィジェットHelloWorldWidget2の名前を更新します。 このアクションにより、フレームワークは拡張機能内のウィジェットを一意に識別できます。

を介して HelloWorldWidget3VSS.register マップされた 関数は、現在、コントラクトを満たす オブジェクトを IWidget 返します。 ウィジェットには構成が必要になったため、コントラクトを満たすオブジェクトを返すには、この関数を更新する IConfigurableWidget 必要があります。 これを行うには、return ステートメントを更新して、次のコードに従って reload というプロパティを含めます。 このプロパティの値は、 メソッドをもう一度呼び出す getQueryInfo 関数です。 この再読み込みメソッドは、ユーザー入力がライブ プレビューを表示するように変更されるたびに、フレームワークによって呼び出されます。 この再読み込みメソッドは、構成を保存するときにも呼び出されます。

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

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

のハードコーディングされたクエリ パス getQueryInfo は、構成されたクエリ パスに置き換える必要があります。これは、 メソッドに渡されるパラメーター widgetSettings から抽出できます。 getQueryInfo メソッドの先頭に次のコードを追加し、ハードコーディングされたクエリ パスをsettings.queryPathに置き換えます。

var settings = JSON.parse(widgetSettings.customSettings.data);
if (!settings || !settings.queryPath) {
    var $container = $('#query-info-container');
    $container.empty();
    $container.text("Sorry nothing to show, please configure a query path.");

    return WidgetHelpers.WidgetStatusHelper.Success();
}

この時点で、ウィジェットは構成された設定でレンダリングする準備ができました。

loadプロパティと プロパティの両方にreload同様の関数があります。 これは、ほとんどの単純なウィジェットの場合です。 複雑なウィジェットの場合、構成の変更回数に関係なく、1 回だけ実行する必要がある特定の操作があります。 または、複数回実行する必要のない重い操作が存在する場合もあります。 このような操作は、 プロパティではなく、 プロパティに load 対応する関数の reload 一部になります。

手順 5: 拡張機能マニフェストを更新する

ファイルを開き、 vss-extension.json プロパティの配列に 2 つの新しいエントリを contributions 含めます。 1 つはウィジェット用 HelloWorldWidget3 、もう 1 つはウィジェットの構成用です。 3 番目のウィジェットには、さらに別のプレビュー イメージが必要です。 この preview3.png 名前を付け、 フォルダーに img 配置します。 この例で追加した 2 つの新しい HTML ファイルを含むように、 files プロパティの配列を更新します。

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

ウィジェット構成のコントリビューションは、ウィジェット自体とは少し異なるモデルに従います。 ウィジェット構成のコントリビューション エントリには、次の項目があります。

  • 投稿を識別する ID 。 ID は拡張機能内で一意である必要があります。
  • コントリビューションの 種類 。 すべてのウィジェット構成では、次のようになります。 ms.vss-dashboards-web.widget-configuration
  • コントリビューションが貢献している ターゲット の配列。 すべてのウィジェット構成に対して、1 つのエントリ ( ms.vss-dashboards-web.widget-configuration) があります。
  • 構成に使用される HTML ファイルの名前、説明、URI を含む一連のプロパティを含む プロパティ

構成をサポートするには、ウィジェットのコントリビューションも変更する必要があります。 ウィジェットのターゲットの配列を更新して、構成の ID を 形式<>>>publisherid for the extension<<id for the configuration contributionで含める必要があります。この場合は です。fabrikam.vsts-extensions-myExtensions.HelloWorldWidget.Configuration

警告

構成可能なウィジェットのコントリビューション エントリが、前述のように適切な発行元と拡張機能の名前を使用して構成を対象としていない場合、ウィジェットの [構成] ボタンは表示されません。

このパートの最後には、マニフェスト ファイルに 3 つのウィジェットと 1 つの構成が含まれている必要があります。 サンプルから完全なマニフェストを取得できます。

手順 6: パッケージ化、発行、共有

拡張機能が公開されていない場合は、このセクション 参照。 拡張機能を既に公開している場合は、拡張機能を再パッケージ化し、Marketplace に直接更新できます。

手順 7: カタログからウィジェットを追加する

次に、 のチーム ダッシュボードに移動します。 https://dev.azure.com/{Your_Organization}/{Your_Project}. このページが既に開いている場合は、更新します。 Editにカーソルを合わせ、[追加選択。 このアクションにより、インストールしたウィジェットが見つかるウィジェット カタログが開きます。 ウィジェットをダッシュボードに追加するには、ウィジェットを選択し、 追加を選択します。

次のようなメッセージが表示され、ウィジェットを構成するように求められます。

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

ウィジェットを構成するには、2 つの方法があります。 1 つは、ウィジェットにカーソルを合わせ、右上隅に表示される省略記号を選択し、[構成] を選択することです。 もう 1 つは、ダッシュボードの右下にある [編集] ボタンを選択し、ウィジェットの右上隅に表示される構成ボタンを選択することです。 どちらかが右側に構成エクスペリエンスを開き、中央にウィジェットのプレビューを開きます。 先に進み、ドロップダウンからクエリを選択します。 ライブ プレビューには、更新された結果が表示されます。 保存を選択すると、ウィジェットに更新された結果が表示されます。

手順 8: さらに構成する (省略可能)

configuration.htmlで必要な数の HTML フォーム要素を追加して、より多くの構成を行うことができます。 すぐに使用できる構成可能な機能は、ウィジェット名とウィジェットサイズの 2 つあります。

既定では、拡張機能マニフェストでウィジェットに指定した名前は、ダッシュボードに追加されるウィジェットのすべてのインスタンスのウィジェット名として格納されます。 ユーザーがウィジェットのインスタンスに任意の名前を追加できるように、構成を許可できます。 このような構成を許可するには、拡張機能マニフェストのウィジェットの [プロパティ] セクションに を追加 isNameConfigurable:true します。

拡張機能マニフェストの配列に supportedSizes ウィジェットに複数のエントリを指定した場合、ユーザーはウィジェットのサイズも構成できます。

このガイドの 3 番目のサンプルの拡張機能マニフェストは、ウィジェットの名前とサイズの構成を有効にした場合の次の例のようになります。

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

前の変更により、拡張機能を 再パッケージ化 して 更新 します。 このウィジェットを含むダッシュボードを更新します (Hello Worldウィジェット 3 (構成あり) )。 ウィジェットの構成モードを開くと、ウィジェットの名前とサイズを変更するオプションが表示されます。

名前とサイズを構成できるウィジェットを示すスクリーンショット。

ドロップダウンから別のサイズを選択します。 ライブ プレビューのサイズが変更されます。 変更を保存すると、ダッシュボードのウィジェットのサイズも変更されます。

ウィジェットの名前を変更しても、サンプル ウィジェットにはウィジェット名が表示されないため、ウィジェットに表示される変更はありません。 ハードコーディングされたテキスト "Hello World" の代わりにウィジェット名を表示するようにサンプル コードを変更してみましょう。

これを行うには、ハードコーディングされたテキスト "Hello World" を、h2要素のテキストを設定する行のwidgetSettings.nameに置き換えます。 このアクションにより、ページの更新時にウィジェットが読み込まれるたびにウィジェット名が表示されます。 構成が変更されるたびにライブ プレビューを更新する必要があるため、コードの部分にも同じコードを reload 追加する必要があります。 の最後の hello-world3.html return ステートメントは次のとおりです。

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

        return getQueryInfo(widgetSettings);
    },
    reload: function (widgetSettings) {
        // Set your title
        var $title = $('h2.title');
        $title.text(widgetSettings.name);

        return getQueryInfo(widgetSettings);
    }
}

拡張機能をもう一度再パッケージ化 して 更新 します。 このウィジェットを含むダッシュボードを更新します。

ウィジェット名に変更が加えられた場合は、構成モードでウィジェットのタイトルを更新します。