Bagikan melalui


Tambahkan widget dasbor

Layanan Azure DevOps | Azure DevOps Server | Azure DevOps Server 2022

Widget diimplementasikan sebagai kontribusi dalam kerangka kerja ekstensi. Satu ekstensi dapat mencakup beberapa kontribusi widget. Artikel ini memperlihatkan cara membuat ekstensi yang menyediakan satu atau beberapa widget.

Petunjuk / Saran

Periksa dokumentasi terbaru kami tentang menggunakan Azure DevOps Extension SDK untuk pengembangan ekstensi.

Petunjuk / Saran

Jika Anda memulai ekstensi Azure DevOps baru, coba koleksi sampel yang dikelola ini terlebih dahulu—mereka bekerja dengan build produk saat ini dan mencakup skenario modern (misalnya, menambahkan tab pada halaman permintaan pull).

Jika sampel tidak berfungsi di organisasi Anda, instal ke organisasi pribadi atau pengujian dan bandingkan ID target manifes ekstensi dan versi API dengan dokumen saat ini. Untuk referensi dan API, lihat:

Prasyarat

Persyaratan Deskripsi
Pengetahuan pemrograman Pengetahuan JavaScript, HTML, dan CSS untuk pengembangan widget
Organisasi Azure DevOps Membuat organisasi
Editor teks Kami menggunakan Visual Studio Code untuk tutorial
Node.js Versi terbaru Node.js
CLI lintas platform tfx-cli untuk memaketkan ekstensi
Instal menggunakan: npm i -g tfx-cli
Direktori proyek Direktori beranda dengan struktur ini setelah menyelesaikan tutorial:

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

Ikhtisar tutorial

Tutorial ini mengajarkan pengembangan widget melalui tiga contoh progresif:

Bagian Fokus Yang Anda pelajari
Bagian 1: Halo Dunia Pembuatan widget dasar Membuat widget yang menampilkan teks
Bagian 2: Integrasi REST API Panggilan API Azure DevOps Menambahkan fungsionalitas REST API untuk mengambil dan menampilkan data
Bagian 3: Konfigurasi widget Kustomisasi pengguna Menerapkan opsi konfigurasi untuk widget Anda

Petunjuk / Saran

Jika Anda lebih suka melompat langsung ke contoh kerja, sampel yang disertakan (lihat catatan sebelumnya) menunjukkan sekumpulan widget yang dapat Anda kemas dan terbitkan.

Sebelum Memulai, tinjau gaya widget dasar dan panduan struktural yang kami berikan.

Bagian 1: Halo Dunia

Buat widget dasar yang menampilkan "Halo Dunia" menggunakan JavaScript. Fondasi ini menunjukkan konsep pengembangan widget inti.

Cuplikan layar dasbor Gambaran Umum dengan widget sampel.

Langkah 1: Instal SDK klien

VSS SDK memungkinkan widget Anda berkomunikasi dengan Azure DevOps. Instal menggunakan npm:

npm install vss-web-extension-sdk

VSS.SDK.min.js Salin file dari vss-web-extension-sdk/lib ke folder Andahome/sdk/scripts.

Untuk dokumentasi SDK lainnya, lihat halaman GitHub SDK Klien.

Langkah 2: Buat struktur HTML

Buat hello-world.html di direktori proyek Anda. File ini menyediakan tata letak dan referensi widget ke skrip yang diperlukan.

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

Widget berjalan di iframe, sehingga sebagian besar elemen kepala HTML kecuali <script> dan <link> diabaikan oleh kerangka kerja.

Langkah 3: Tambahkan widget JavaScript

Untuk menerapkan fungsionalitas widget, tambahkan skrip ini ke bagian <head> file HTML Anda:

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

Komponen Kunci JavaScript

Fungsi Tujuan
VSS.init() Menginisialisasi komunikasi antara widget dan Azure DevOps
VSS.require() Memuat pustaka SDK dan pembantu widget yang diperlukan
VSS.register() Mendaftarkan widget Anda dengan pengidentifikasi unik
WidgetHelpers.IncludeWidgetStyles() Menerapkan pengaturan gaya default Azure DevOps
VSS.notifyLoadSucceeded() Memberi tahu kerangka kerja bahwa pemuatan berhasil diselesaikan

Penting

Nama widget di VSS.register() harus cocok dengan id di manifes ekstensi Anda (Langkah 5).

Langkah 4: Tambahkan gambar ekstensi

Buat gambar yang diperlukan untuk ekstensi Anda:

  • Logo ekstensi: Gambar 98x98 piksel bernama logo.png dalam img folder
  • Ikon katalog widget: Gambar piksel 98x98 bernama CatalogIcon.png di img folder
  • Pratinjau widget: Gambar piksel 330x160 bernama preview.png di img folder

Gambar-gambar ini ditampilkan di Marketplace dan katalog widget saat pengguna menelusuri ekstensi yang tersedia.

Langkah 5: Membuat manifes ekstensi

Buat vss-extension.json di direktori akar proyek Anda. File ini menentukan metadata dan kontribusi ekstensi Anda:

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

Penting

Ganti "publisher": "fabrikam" dengan nama penerbit Anda yang sebenarnya. Pelajari cara membuat penerbit.

Properti asas manifes penting

Bagian Tujuan
Info dasar Nama ekstensi, versi, deskripsi, dan penerbit
Ikon Lokasi elemen visual perluasan Anda
Kontribusi Definisi widget termasuk ID, jenis, dan properti
File Semua file yang akan disertakan dalam paket ekstensi

Untuk dokumentasi manifes lengkap, lihat Referensi manifes ekstensi.

Langkah 6: Mengemas dan menerbitkan ekstensi Anda

Kemas ekstensi Anda dan terbitkan ke Visual Studio Marketplace.

Menginstal alat pengemasan

npm i -g tfx-cli

Membuat paket ekstensi Anda

Dari direktori proyek Anda, jalankan:

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

Tindakan ini membuat berkas .vsix yang berisi paket ekstensi Anda.

Menyiapkan penerbit

  1. Buka Portal Penerbitan Marketplace Visual Studio.
  2. Silakan masuk dan buat akun penerbit jika Anda belum memilikinya.
  3. Pilih pengidentifikasi penerbit unik (digunakan dalam file manifes Anda).
  4. Perbarui vss-extension.json untuk menggunakan nama penerbit Anda, bukan "fabrikam."

Unggah ekstensi Anda

  1. Di Portal Penerbitan, pilih Unggah ekstensi baru.
  2. Pilih file Anda .vsix dan unggah.
  3. Bagikan ekstensi dengan organisasi Azure DevOps Anda.

Atau, gunakan baris perintah:

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

Petunjuk / Saran

Gunakan --rev-version untuk secara otomatis menaikkan nomor versi saat memperbarui ekstensi yang ada.

Langkah 7: Menginstal dan menguji widget Anda

Untuk menguji, tambahkan widget Anda ke dasbor:

  1. Buka proyek Azure DevOps Anda: https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Buka Gambaran Umum>Dasbor.
  3. Pilih Tambahkan widget.
  4. Temukan widget Anda di katalog dan pilih Tambahkan.

Widget "Halo Dunia" Anda muncul di dasbor, menampilkan teks yang Anda konfigurasikan.

Langkah selanjutnya: Lanjutkan ke Bagian 2 untuk mempelajari cara mengintegrasikan REST API Azure DevOps ke widget Anda.

Bagian 2: Halo Dunia dengan REST API Azure DevOps

Perluas widget Anda untuk berinteraksi dengan data Azure DevOps menggunakan REST API. Contoh ini menunjukkan cara mengambil informasi kueri dan menampilkannya secara dinamis di widget Anda.

Di bagian ini, gunakan REST API Pelacakan Item Kerja untuk mengambil informasi tentang kueri yang ada dan menampilkan detail kueri di bawah teks "Halo Dunia".

Cuplikan layar dasbor Gambaran Umum dengan widget sampel menggunakan REST API untuk WorkItemTracking.

Langkah 1: Buat file HTML yang disempurnakan

Buat file widget baru yang dibangun pada contoh sebelumnya. Salin hello-world.html dan ganti namanya menjadi hello-world2.html. Struktur proyek Anda sekarang mencakup:

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

Memperbarui struktur HTML widget

Buat perubahan ini pada hello-world2.html:

  1. Menambahkan kontainer untuk data kueri: Sertakan elemen baru <div> untuk menampilkan informasi kueri.
  2. Perbarui pengidentifikasi widget: Ubah nama widget dari HelloWorldWidget ke HelloWorldWidget2 untuk identifikasi unik.
<!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>

Langkah 2: Mengonfigurasi izin akses API

Sebelum melakukan panggilan REST API, konfigurasikan izin yang diperlukan dalam manifes ekstensi Anda.

Menambahkan cakupan kerja

Cakupan vso.work memberikan akses baca-saja ke item dan kueri kerja. Tambahkan cakupan ini ke vss-extension.json:

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

Contoh manifes lengkap

Untuk manifes lengkap dengan properti lain, susun seperti ini:

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

Penting

Batasan cakupan: Menambahkan atau mengubah cakupan setelah penerbitan tidak didukung. Jika Anda sudah menerbitkan ekstensi, Anda harus menghapusnya dari Marketplace terlebih dahulu. Buka Portal Penerbitan Marketplace Visual Studio, temukan ekstensi Anda, dan pilih Hapus.

Langkah 3: Menerapkan integrasi REST API

Azure DevOps menyediakan pustaka klien JavaScript REST melalui SDK. Pustaka ini membungkus panggilan AJAX dan memetakan respons API ke objek yang dapat digunakan.

Memperbarui widget JavaScript

Ganti panggilan VSS.require dalam hello-world2.html Anda untuk menambahkan klien REST Pelacakan Item Kerja:

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

Detail implementasi utama

Komponen Tujuan
WorkItemTrackingRestClient.getClient() Mendapatkan instans REST client untuk Pelacakan Item Kerja
getQuery() Mengambil informasi kueri yang dibungkus dalam janji
WidgetStatusHelper.Failure() Menyediakan penanganan kesalahan yang konsisten untuk kegagalan widget
projectId Konteks proyek saat ini diperlukan untuk panggilan API

Petunjuk / Saran

Jalur kueri kustom: Jika Anda tidak memiliki kueri "Umpan Balik" di "Kueri Bersama", ganti "Shared Queries/Feedback" dengan jalur ke kueri apa pun yang ada di proyek Anda.

Langkah 4: Menampilkan data respons API

Render informasi kueri di widget Anda dengan memproses respons REST API.

Menambahkan pemrosesan data kueri

// Process query data Ganti komentar dengan implementasi ini:

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

Metode getQuery() mengembalikan Contracts.QueryHierarchyItem objek dengan properti untuk metadata kueri. Contoh ini menampilkan tiga bagian utama informasi di bawah teks "Halo Dunia".

Contoh kerja lengkap

File akhir hello-world2.html Anda akan terlihat seperti ini:

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

Langkah 5: Memperbarui manifes ekstensi

Untuk membuatnya tersedia di katalog widget, tambahkan widget baru Anda ke manifes ekstensi.

Menambahkan kontribusi widget kedua

Perbarui vss-extension.json untuk menyertakan widget yang diaktifkan REST API Anda. Tambahkan kontribusi ini ke contributions array:

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

Petunjuk / Saran

Gambar pratinjau: Buat preview2.png gambar (330x160 piksel) dan letakkan di img folder untuk menampilkan tampilan widget Anda kepada pengguna di katalog.

Langkah 6: Mengemas, menerbitkan, dan berbagi

Paket, terbitkan, dan bagikan ekstensi Anda. Jika Anda sudah menerbitkan ekstensi, Anda dapat mengemas ulang dan memperbaruinya langsung di Marketplace.

Langkah 7: Uji widget REST API Anda

Untuk melihat tindakan integrasi REST API, tambahkan widget baru ke dasbor Anda:

  1. Buka proyek Azure DevOps Anda: https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Pilih Gambaran Umum>Dasbor.
  3. Pilih Tambahkan widget.
  4. Temukan "Hello World Widget 2 (dengan API)" dan pilih Tambahkan.

Widget Anda yang disempurnakan menampilkan teks "Halo Dunia" dan informasi kueri langsung dari proyek Azure DevOps Anda.

Langkah berikutnya: Lanjutkan ke Bagian 3 untuk menambahkan opsi konfigurasi yang memungkinkan pengguna menyesuaikan kueri mana yang akan ditampilkan.

Bagian 3: Mengonfigurasi Halo Dunia

Kembangkan dari Bagian 2 dengan menambahkan fitur konfigurasi pengguna ke widget Anda. Alih-alih mengkodekan jalur kueri secara permanen, buat antarmuka konfigurasi yang memungkinkan pengguna memilih kueri mana yang akan ditampilkan, dengan fungsionalitas pratinjau langsung.

Bagian ini menunjukkan cara membuat widget yang dapat dikonfigurasi yang dapat disesuaikan pengguna dengan kebutuhan spesifik mereka sambil memberikan umpan balik real-time selama konfigurasi.

Cuplikan layar pratinjau langsung dasbor Gambaran Umum widget berdasarkan perubahan.

Langkah 1: Membuat file konfigurasi

Konfigurasi widget berbagi banyak kesamaan dengan widget itu sendiri—keduanya menggunakan pola SDK, struktur HTML, dan JavaScript yang sama, tetapi melayani tujuan yang berbeda dalam kerangka kerja ekstensi.

Menyiapkan struktur proyek

Untuk mendukung konfigurasi widget, buat dua file baru:

  1. Salin hello-world2.html dan ganti namanya menjadi hello-world3.html, widget yang dapat dikonfigurasi.
  2. Buat file baru bernama configuration.html, yang menangani antarmuka konfigurasi.

Struktur proyek Anda sekarang mencakup:

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

Membuat antarmuka konfigurasi

Tambahkan struktur HTML ini ke configuration.html, yang membuat pemilih dropdown untuk memilih kueri:

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

Langkah 2: Menerapkan konfigurasi JavaScript

Konfigurasi JavaScript mengikuti pola inisialisasi yang sama dengan widget, tetapi mengimplementasikan IWidgetConfiguration kontrak alih-alih kontrak dasar IWidget.

Menambahkan logika konfigurasi

Sisipkan skrip ini ke <head> bagian dari 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>

Detail kontrak konfigurasi

Kontrak IWidgetConfiguration memerlukan fungsi utama ini:

Fungsi Tujuan Ketika dipanggil
load() Inisialisasi antarmuka konfigurasi dengan pengaturan yang sudah ada Saat dialog konfigurasi terbuka
onSave() Menserialisasikan input pengguna dan memvalidasi pengaturan Saat pengguna memilih Simpan

Petunjuk / Saran

Serialisasi data: Contoh ini menggunakan JSON untuk menserialisasikan pengaturan. Widget mengakses pengaturan ini melalui widgetSettings.customSettings.data dan harus mendeserialisasinya dengan sesuai.

Langkah 3: Mengaktifkan fungsionalitas pratinjau langsung

Pratinjau langsung memungkinkan pengguna untuk melihat perubahan widget segera saat mereka memodifikasi pengaturan konfigurasi, memberikan umpan balik instan sebelum menyimpan.

Menerapkan pemberitahuan perubahan

Untuk mengaktifkan pratinjau langsung, tambahkan penanganan aktivitas ini dalam load fungsi:

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

Selesaikan file konfigurasi

Akhir configuration.html Anda akan terlihat seperti ini:

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

Penting

Tombol Aktifkan Simpan: Kerangka kerja memerlukan setidaknya satu pemberitahuan perubahan konfigurasi untuk mengaktifkan tombol Simpan . Penanganan aktivitas perubahan memastikan tindakan ini terjadi saat pengguna memilih opsi.

Langkah 4: Membuat widget dapat dikonfigurasi

Ubah widget Anda dari Bagian 2 untuk menggunakan data konfigurasi alih-alih nilai yang dikodekan secara permanen. Langkah ini memerlukan penerapan kontrak IConfigurableWidget.

Memperbarui pendaftaran widget

Di hello-world3.html, buat perubahan ini:

  1. Perbarui ID widget: Ubah dari HelloWorldWidget2 ke HelloWorldWidget3.
  2. Tambahkan fungsi muat ulang: Terapkan IConfigurableWidget kontrak.
return {
    load: function (widgetSettings) {
        // Set your title
        var $title = $('h2.title');
        $title.text('Hello World');

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

Menangani data konfigurasi

getQueryInfo Perbarui fungsi untuk menggunakan pengaturan konfigurasi alih-alih jalur kueri yang dikodekan secara permanen:

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

Perbedaan siklus hidup widget

Fungsi Tujuan Panduan penggunaan
load() Penyajian widget awal dan penyiapan satu kali Operasi berat, inisialisasi sumber daya
reload() Memperbarui widget dengan konfigurasi baru Pembaruan ringan, penyegaran data

Petunjuk / Saran

Pengoptimalan performa: Gunakan load() untuk operasi mahal yang hanya perlu berjalan sekali, dan reload() untuk pembaruan cepat saat konfigurasi berubah.

(Opsional) Menambahkan lightbox untuk informasi terperinci

Widget dasbor memiliki ruang terbatas, sehingga menantang untuk menampilkan informasi yang komprehensif. Lightbox memberikan solusi yang elegan dengan menampilkan data terperinci dalam overlay modal tanpa berpindah dari dasbor.

Mengapa menggunakan lightbox di widget?

Keuntungan Deskripsi
Efisiensi ruang Jaga widget tetap ringkas saat menawarkan tampilan terperinci
Pengalaman pengguna Pertahankan konteks dasbor sambil menampilkan informasi lebih lanjut
Pengungkapan progresif Tampilkan data ringkasan di widget, detail sesuai permintaan
desain responsif Beradaptasi dengan ukuran layar dan konfigurasi widget yang berbeda

Menerapkan elemen yang dapat diklik

Perbarui penyajian data kueri Anda untuk menyertakan elemen yang dapat diklik yang memicu lightbox:

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

// Add a clickable element to open detailed view
var $detailsLink = $('<button class="details-link">View Details</button>');
$detailsLink.on('click', function() {
    showQueryDetails(query);
});

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

Mengembangkan fungsi lightbox

Tambahkan implementasi lightbox ini ke javaScript widget Anda:

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

Menambahkan gaya lightbox

Sertakan gaya CSS untuk lightbox di bagian HTML <head> widget Anda:

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

Implementasi widget yang ditingkatkan

Widget lengkap Anda yang disempurnakan dengan fungsionalitas lightbox:

<!DOCTYPE html>
<html>
<head>    
    <script src="sdk/scripts/VSS.SDK.min.js"></script>
    <style>
        /* Lightbox styles from above */
        .query-summary {
            list-style: none;
            padding: 0;
            margin: 10px 0;
        }
        
        .query-summary li {
            padding: 2px 0;
            font-size: 12px;
        }
        
        .details-link {
            background: #0078d4;
            color: white;
            border: none;
            padding: 4px 8px;
            font-size: 11px;
            cursor: pointer;
            border-radius: 2px;
            margin-top: 8px;
        }
        
        .details-link:hover {
            background: #106ebe;
        }
        
        .lightbox-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.7);
            z-index: 10000;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        
        .lightbox-content {
            background: white;
            border-radius: 4px;
            padding: 20px;
            max-width: 500px;
            max-height: 80vh;
            overflow-y: auto;
            position: relative;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
        }
        
        .lightbox-close {
            position: absolute;
            top: 10px;
            right: 15px;
            background: none;
            border: none;
            font-size: 24px;
            cursor: pointer;
            color: #666;
            line-height: 1;
        }
        
        .lightbox-close:hover {
            color: #000;
        }
        
        .query-details h3 {
            margin-top: 0;
            color: #323130;
        }
        
        .query-details p {
            margin: 8px 0;
            font-size: 14px;
            line-height: 1.4;
        }
    </style>
    <script type="text/javascript">
        VSS.init({
            explicitNotifyLoaded: true,
            usePlatformStyles: true
        });

        VSS.require(["AzureDevOps/Dashboards/WidgetHelpers", "AzureDevOps/WorkItemTracking/RestClient"], 
            function (WidgetHelpers, WorkItemTrackingRestClient) {
                WidgetHelpers.IncludeWidgetStyles();
                
                function showQueryDetails(query) {
                    // Lightbox implementation from above
                }
                
                VSS.register("HelloWorldWidget2", function () {                
                    var projectId = VSS.getWebContext().project.id;

                    var getQueryInfo = function (widgetSettings) {
                        return WorkItemTrackingRestClient.getClient().getQuery(projectId, "Shared Queries/Feedback")
                            .then(function (query) {
                                // Enhanced display with lightbox trigger
                                var $list = $('<ul class="query-summary">');                                
                                $list.append($('<li>').text("Query ID: " + query.id));
                                $list.append($('<li>').text("Query Name: " + query.name));
                                $list.append($('<li>').text("Created By: " + (query.createdBy ? query.createdBy.displayName : "<unknown>")));

                                var $detailsLink = $('<button class="details-link">View Details</button>');
                                $detailsLink.on('click', function() {
                                    showQueryDetails(query);
                                });

                                var $container = $('#query-info-container');
                                $container.empty();
                                $container.append($list);
                                $container.append($detailsLink);

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

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

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

Pertimbangan aksesibilitas: Pastikan lightbox Anda dapat diakses keyboard dan menyertakan label yang tepat untuk pembaca layar. Uji dengan fitur aksesibilitas bawaan Azure DevOps.

Penting

Performa: Lightbox harus dimuat dengan cepat. Pertimbangkan untuk memuat data terperinci secara tertunda hanya saat lightbox terbuka, daripada mengambil semuanya di awal.

Langkah 5: Mengonfigurasi manifes ekstensi

Daftarkan widget yang dapat dikonfigurasi dan antarmuka konfigurasinya di manifes ekstensi Anda.

Menambahkan kontribusi widget dan konfigurasi

Perbarui vss-extension.json untuk menyertakan dua kontribusi baru:

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

Persyaratan kontribusi konfigurasi

Properti Tujuan Nilai yang diperlukan
type Mengidentifikasi kontribusi sebagai pengaturan widget ms.vss-dashboards-web.widget-configuration
targets Tempat konfigurasi muncul ms.vss-dashboards-web.widget-configuration
uri Jalur ke file HTML konfigurasi Jalur file konfigurasi Anda

Pola penargetan widget

Untuk widget yang dapat dikonfigurasi targets , array harus menyertakan referensi ke konfigurasi:

<publisher>.<extension-id>.<configuration-id>

Peringatan

Visibilitas tombol konfigurasi: Jika widget tidak menargetkan kontribusi konfigurasinya dengan benar, tombol Konfigurasikan tidak muncul. Verifikasi bahwa nama penerbit dan ekstensi sama persis dengan manifes Anda.

Langkah 6: Mengemas, menerbitkan, dan berbagi

Sebarkan ekstensi yang disempurnakan dengan kemampuan konfigurasi.

Jika ini adalah publikasi pertama Anda, ikuti Langkah 6: Paket, terbitkan, dan bagikan. Untuk ekstensi yang ada, kemas ulang dan perbarui langsung di Marketplace.

Langkah 7: Uji widget yang dapat dikonfigurasi

Rasakan alur kerja konfigurasi lengkap dengan menambahkan dan mengonfigurasi widget Anda.

Menambahkan widget ke dasbor Anda

  1. Pergi ke https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Buka Gambaran Umum>Dasbor.
  3. Pilih Tambahkan widget.
  4. Temukan "Hello World Widget 3 (dengan konfigurasi)" dan pilih Tambahkan.

Perintah konfigurasi ditampilkan karena widget memerlukan penyiapan:

Cuplikan layar dasbor Gambaran Umum dengan widget sampel dari katalog.

Mengonfigurasi widget

Konfigurasi akses melalui salah satu metode:

  • Menu Widget: Arahkan mouse ke atas widget, pilih elipsis (⋯), lalu Konfigurasikan
  • Mode edit dasbor: Pilih Edit di dasbor, lalu tombol konfigurasikan pada widget

Panel konfigurasi terbuka dengan pratinjau langsung di tengah. Pilih kueri dari menu dropdown untuk melihat pembaruan langsung, lalu pilih Simpan untuk menerapkan perubahan Anda.

Langkah 8: Tambahkan opsi konfigurasi tingkat lanjut

Perluas widget Anda dengan lebih banyak fitur konfigurasi bawaan seperti nama dan ukuran kustom.

Mengaktifkan konfigurasi nama dan ukuran

Azure DevOps menyediakan dua fitur yang dapat dikonfigurasi secara langsung:

Fitur Properti manifes Tujuan
Nama Khusus isNameConfigurable: true Pengguna dapat mengambil alih nama widget default
Beberapa ukuran Beberapa supportedSizes entri Pengguna dapat mengubah ukuran widget

Contoh manifes yang telah ditingkatkan

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

Menampilkan nama yang dikonfigurasi

Untuk menampilkan nama widget kustom, perbarui widget Anda untuk menggunakan 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);
    }
}

Setelah memperbarui ekstensi, Anda dapat mengonfigurasi nama dan ukuran widget:

Cuplikan layar memperlihatkan di mana nama dan ukuran widget dapat dikonfigurasi.

Kemas ulang dan perbarui ekstensi Anda untuk mengaktifkan opsi konfigurasi tingkat lanjut ini.

Selamat! Anda membuat widget dasbor Azure DevOps yang lengkap dan dapat dikonfigurasi dengan kemampuan pratinjau langsung dan opsi penyesuaian pengguna.