次の方法で共有


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

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.jsの最新バージョン

  • 拡張機能をパッケージ化するための Azure DevOps 用クロスプラットフォーム CLI (tfx-cli )。

    • tfx-cli は、Node.js のコンポーネントである npm を使用してインストールできます。 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
    

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

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

ウィジェットの基本的なスタイルをすぐに使えるように準備し、ウィジェットの構造に関するガイダンスを提供します。

パート 1: Hello World

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

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

手順 1: クライアント SDK を取得する

コア SDK スクリプト VSS.SDK.min.jsにより、Web 拡張機能はホスト Azure DevOps フレームと通信できます。 スクリプトは、初期化、拡張機能の読み込み通知、または現在のページに関するコンテキストの取得などの操作を実行します。

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

npm install vss-web-extension-sdk

VSS.SDK.min.js フォルダーにあるクライアント SDK vss-web-extension-sdk/lib ファイルを見つけて、home/sdk/scripts フォルダーに配置します。

詳細については、クライアント SDK GitHub のページを参照してください。

手順 2: HTML ページを設定する

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

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

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

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

手順 3: JavaScript を更新する

JavaScript を使用して、ウィジェット内のコンテンツをレンダリングします。 この記事では、すべての JavaScript コードを HTML ファイル内の <script> 要素内にラップします。 このコードを別の JavaScript ファイルに含め、HTML ファイルで参照することもできます。

コードはコンテンツをレンダリングします。 この JavaScript コードでは、VSS SDK も初期化され、ウィジェットのコードがウィジェット名にマップされ、ウィジェットの成功または失敗が拡張機能フレームワークに通知されます。

この場合、次のコードは ウィジェットに Hello World を出力します。 この script 要素を HTML の head に追加します。

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

  • は、ウィジェットをホストしているアイフレームとホストフレームの間のハンドシェイクを初期化します。

  • 読み込みが完了したときにウィジェットがホストに明示的に通知できるように、 explicitNotifyLoaded: true を渡します。 このコントロールを使用すると、依存モジュールが読み込まれたことを確認した後、読み込みの完了を通知できます。 ウィジェットで HTML 要素 (usePlatformStyles: truebodyなど) に Azure DevOps コア スタイルを使用できるように、divを使用します。 ウィジェットでこれらのスタイルを使用しない場合は、 usePlatformStyles: false渡します。

  • VSS.require は、必要な VSS スクリプト ライブラリを読み込むのに使用されます。 このメソッドを呼び出すと、 JQuery や JQueryUI などの一般的なライブラリが自動的に読み込 まれます。 ここでは、ウィジェット フレームワークにウィジェットの状態を伝えるために使用される WidgetHelpers ライブラリに依存しています。 そのため、対応するモジュール名 TFS/Dashboards/WidgetHelpers とコールバックを VSS.requireに渡します。 コールバックは、モジュールが読み込まれた後に呼び出されます。 コールバックには、ウィジェットに必要な残りの JavaScript コードがあります。 コールバックの最後に、 VSS.notifyLoadSucceeded を呼び出して、読み込みの完了を通知します。

  • WidgetHelpers.IncludeWidgetStyles には、作業を開始するための 基本的な CSS を含むスタイルシートが含まれています。 これらのスタイルを使用するには、クラス widget を使用した HTML 要素内でコンテンツを包み込みます。

  • VSS.registerは、JavaScriptで関数をマッピングして、拡張機能内のさまざまな寄与の中でウィジェットを一意に識別するために使用されます。 この関数の名前は、id で説明したコントリビューションを識別する と一致する必要があります。 ウィジェットの場合、 VSS.register に渡される関数は、 IWidget コントラクトを満たすオブジェクトを返す必要があります。 たとえば、返されるオブジェクトには、ウィジェットをレンダリングするコア ロジックを持つ別の関数である値を持つ load プロパティが必要です。 ここでは、 h2 要素のテキストを Hello World に更新します。 これは、ウィジェット フレームワークがウィジェットをインスタンス化するときに呼び出されるこの関数です。 ここでは WidgetHelpers の WidgetStatusHelper を使用して、WidgetStatus を success として返します。

    警告

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

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

98 x 98 ピクセルのカタログ アイコンが必要です。 イメージを選択し、logo.pngという名前を付けて、imgフォルダーに配置します。 イメージには、次の手順の拡張機能マニフェストが使用する名前で更新されている限り、必要に応じて名前を付けることができます。

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

すべての 拡張機能には、拡張マニフェスト ファイルが必要です。 vss-extension.json ディレクトリに home という名前の 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
        }
    ]
}

vss-extension.json ファイルは、常にホーム ディレクトリのルートにある必要があります。 他のすべてのファイルについては、フォルダー内の任意の構造に配置できますが、HTML ファイルおよび vss-extension.json マニフェストで参照を適切に更新してください。

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

publisherを発行元名に変更します。 パブリッシャーを作成するには、パッケージ/発行/インストールに関する記事を参照してください。

アイコン

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

貢献

各コントリビューション エントリは特性を定義します。

  • 投稿を識別するための id 。 この ID は拡張機能内で一意である必要があります。 このIDは、手順3でウィジェットを登録する際に使用した名前と一致している必要があります。

  • コントリビューションの type。 すべてのウィジェットの型は、ms.vss-dashboards-web.widget である必要があります。

  • コントリビューションの対象となる targets の配列。 すべてのウィジェットのターゲットは[ms.vss-dashboards-web.widget-catalog]である必要があります。

  • propertiesは、コントリビューション型のプロパティを含むオブジェクトです。 ウィジェットの場合、次のプロパティが必須です。

    プロパティ 内容
    name ウィジェット カタログに表示するウィジェットの名前。
    description ウィジェット カタログに表示するウィジェットの説明。
    catalogIconUrl ウィジェットカタログに表示するために手順 4 で追加されたカタログアイコンの相対パス。 イメージは 98 x 98 ピクセルである必要があります。 別のフォルダー構造または別のファイル名を使用した場合は、ここで適切な相対パスを指定します。
    previewImageUrl ウィジェット カタログに表示するために Step 4 で追加したプレビューイメージの相対パス。 イメージは 330 x 160 ピクセルである必要があります。 別のフォルダー構造または別のファイル名を使用した場合は、ここで適切な相対パスを指定します。
    uri 手順 1 で追加したHTMLファイルの相対パスです。 別のフォルダー構造または別のファイル名を使用した場合は、ここで適切な相対パスを指定します。
    supportedSizes ウィジェットでサポートされているサイズの配列。 ウィジェットで複数のサイズがサポートされている場合、配列の最初のサイズはウィジェットの既定のサイズです。 widget size は、ダッシュボード グリッド内でウィジェットが占有する行と列に対して指定されます。 1 つの行/列が 160 px に対応します。 1x1 より大きいディメンションでは、ウィジェット間の余白を表す 10 px が追加されます。 たとえば 3x2 のウィジェットは、幅が160*3+10*2、高さが 160*2+10*1 になります。 サポートされる最大サイズは 4 x 4 です。
    supportedScopes 現時点では、チーム ダッシュボードのみがサポートされています。 値は project_team である必要があります。 今後の更新には、ダッシュボード スコープのオプションが追加される可能性があります。

コントリビューション ポイントの詳細については、「 拡張ポイント」を参照してください。

ファイル

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

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

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

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

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

Node.jsのコンポーネントである npm を使用して、コマンド ラインから tfx-cli をインストールまたは更新できます。

npm i -g tfx-cli

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

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

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

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

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

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

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

  1. Visual Studio Marketplace 発行ポータルにサインインします。

  2. 既存のパブリッシャーのメンバーでない場合は、パブリッシャーを作成する必要があります。 パブリッシャーが既にある場合は、Related Sites の下にある Publish Extensions をスクロールして選択してください。

    • 発行元の識別子を指定します (例: mycompany-myteam)。
      • 識別子は、拡張機能のマニフェスト ファイル内の publisher 属性の値として使用されます。
    • 発行元の表示名を指定します (例: マイ チーム)。
  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. [概要]>[ダッシュボード] を選択します。

  3. ウィジェットを追加を選択します。

  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
    |--- scripts
        |--- VSS.SDK.min.js
|--- img
    |--- logo.png
|--- scripts
|--- hello-world.html               // html page to be used for your widget
|--- hello-world2.html              // renamed copy of hello-world.html
|--- vss-extension.json             // extension's manifest

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

<!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 発行ポータルに移動し、拡張機能を右選択して、[削除] を選択します

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

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

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

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

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

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

手順 4: 応答を表示する

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

// Do something with the query コメントを、次のコードに置き換えます。

// Create a list with query details                                
var $list = $('<ul>');                                
$list.append($('<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}) にアクセスします。 このページが既に開いている場合は、更新します。 [編集] にカーソルを合わせ、[追加] を選択します。 ウィジェット カタログが開き、インストールしたウィジェットが見つかります。 ダッシュボードに追加するには、ウィジェットを選択し、 [追加] を選択します。

パート 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 は、パート 1 で説明したウィジェットの場合と同じ役割を果たします。 唯一の違いは、ウィジェット構成の場合、 VSS.register に渡される関数は、 IWidgetConfiguration コントラクトを満たすオブジェクトを返す必要があるということです。

  • load コントラクトの IWidgetConfiguration プロパティには、値として関数が必要です。 この関数には、ウィジェット構成をレンダリングするための一連の手順があります。 ここでは、ドロップダウン要素の選択した値を既存の設定があれば更新します。 この関数は、フレームワークが widget configuration をインスタンス化する際に呼び出されます

  • onSave コントラクトの IWidgetConfiguration プロパティには、値として関数が必要です。 この関数は、ユーザーが構成ウィンドウで [保存] を選択すると、フレームワークによって呼び出されます。 ユーザー入力を保存する準備ができたら、文字列にシリアル化し、 custom settings オブジェクトを形成し、 WidgetConfigurationSave.Valid() を使用してユーザー入力を保存します。

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

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

ユーザーがドロップダウンからクエリを選択したときにライブ プレビューの更新を有効にするには、変更イベント ハンドラーをボタンにアタッチします。 このハンドラーは、構成が変更されたことをフレームワークに通知します。 また、プレビューの更新に使用するためにcustomSettingsを渡します。 フレームワークに通知するには、notifywidgetConfigurationContext メソッドを呼び出す必要があります。 このメソッドは 2 つのパラメーターを受け取ります。1 つはイベント名 (この場合は WidgetHelpers.WidgetEvent.ConfigurationChange)、もう 1 つは 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 ファイルを開き、HelloWorldWidget2 を呼び出す行でウィジェットの名前を HelloWorldWidget3 から VSS.register に更新します。 このアクションにより、フレームワークは拡張機能内のウィジェットを一意に識別できます。

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

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 ファイルを開き、contributions プロパティの配列に 2 つの新しいエントリを含めます。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 は拡張機能内で一意である必要があります。
  • コントリビューションの type。 すべてのウィジェット構成では、 ms.vss-dashboards-web.widget-configurationする必要があります。
  • コントリビューションの対象となる targets の配列。 すべてのウィジェット構成に、ms.vss-dashboards-web.widget-configuration という単一のエントリがあります。
  • 構成に使用される HTML ファイルの名前、説明、および URI を含む一連のプロパティを含む properties

構成をサポートするには、ウィジェットのコントリビューションも変更する必要があります。 ウィジェットの targets の配列を更新して、構成の ID を次の形式で含める必要があります。

<publisher>.<id for the extension>.<id for the configuration contribution>

この場合、例は次のようになります。

fabrikam.vsts-extensions-myExtensions.HelloWorldWidget.Configuration

警告

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

このパートの最後には、マニフェスト ファイルに 3 つのウィジェットと 1 つの構成が含まれている必要があります。 完全なサンプル マニフェスト ファイルについては、 vss-extension.jsonを参照してください。

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

拡張機能が公開されていない場合は、「 手順 6: パッケージ化、発行、共有」を参照してください。 拡張機能を既に公開している場合は、拡張機能を再パッケージ化し、Marketplace に直接更新できます。

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

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

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

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

ウィジェットを構成するには、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 を、要素のテキストを設定した行のwidgetSettings.nameに置き換えて、ハードコーディングされたテキスト h2 ではなくウィジェット名を表示するようにサンプル コードを変更します。 このアクションにより、ページの更新時にウィジェットが読み込まれるたびにウィジェット名が表示されます。 構成が変更されるたびにライブ プレビューを更新する必要があるため、コードの 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);
    }
}

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

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