Freigeben über


Hinzufügen eines Dashboard-Widgets

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

Widgets werden als Beiträge im Erweiterungsrahmen implementiert. Eine einzelne Erweiterung kann mehrere Widgetbeiträge enthalten. In diesem Artikel wird gezeigt, wie Sie eine Erweiterung erstellen, die mindestens ein Widget bereitstellt.

Tipp

Sehen Sie sich unsere neuste Dokumentation zur Erweiterungsentwicklung mithilfe des Azure DevOps Erweiterungs-SDK an.

Tipp

Wenn Sie eine neue Azure DevOps-Erweiterung starten, probieren Sie diese verwalteten Beispielsammlungen zuerst aus – sie arbeiten mit aktuellen Produktbuilds und behandeln moderne Szenarien (z. B. Hinzufügen von Registerkarten auf Pullanforderungsseiten).

Wenn ein Beispiel in Ihrer Organisation nicht funktioniert, installieren Sie es in einer persönlichen oder Testorganisation und vergleichen Sie die Ziel-IDs und API-Versionen des Erweiterungsmanifests mit den aktuellen Dokumenten. Weitere Informationen und APIs finden Sie unter:

Voraussetzungen

Anforderung Beschreibung
Programmierkenntnisse JavaScript-, HTML- und CSS-Kenntnisse für die Widgetentwicklung
Azure DevOps Organisation Eine Organisation erstellen
Texteditor Wir verwenden Visual Studio Code für Lernprogramme
Node.js Neueste Version von Node.js
Plattformübergreifende CLI tfx-cli zum Verpacken von Erweiterungen
Installieren mit: npm i -g tfx-cli
Projektverzeichnis Startverzeichnis mit dieser Struktur nach Abschluss des Tutorials:

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

Übersicht über das Tutorial

In diesem Lernprogramm lernen Sie die Widgetentwicklung anhand von drei progressiven Beispielen kennen:

Teil Fokus Lerninhalt
Teil 1: Hello World Einfache Widgeterstellung Erstellen eines Widgets, das Text anzeigt
Teil 2: REST-API-Integration Azure DevOps-API-Aufrufe Hinzufügen von REST-API-Funktionen zum Abrufen und Anzeigen von Daten
Teil 3: Widgetkonfiguration Benutzeranpassung Implementieren von Konfigurationsoptionen für Ihr Widget

Tipp

Wenn Sie lieber direkt zu funktionierenden Beispielen springen möchten, zeigen die enthaltenen Beispiele (siehe vorherige Notiz) eine Reihe von Widgets an, die Sie packen und veröffentlichen können.

Bevor Sie beginnen, überprüfen Sie die grundlegenden Widgetstile und die strukturelle Anleitungen, die wir bereitstellen.

Teil 1: Hallo Welt

Erstellen Sie ein einfaches Widget, das "Hello World" mit JavaScript anzeigt. Diese Stiftung veranschaulicht die Kernkonzepte für die Widgetentwicklung.

Screenshot des Übersichtsdashboards mit einem Beispiel-Widget.

Schritt 1: Installieren des Client-SDK

Mit dem VSS SDK kann Ihr Widget mit Azure DevOps kommunizieren. Installieren Sie es mithilfe von npm:

npm install vss-web-extension-sdk

Kopieren Sie die VSS.SDK.min.js Datei von vss-web-extension-sdk/lib in Ihren home/sdk/scripts Ordner.

Weitere SDK-Dokumentation finden Sie auf der GitHub-Seite des Client SDK.

Schritt 2: Erstellen der HTML-Struktur

Erstellen Sie hello-world.html in Ihrem Projektverzeichnis. Diese Datei stellt das Layout des Widgets und Verweise auf erforderliche Skripts bereit.

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

Widgets werden in iframes ausgeführt, sodass die meisten HTML-Kopfelemente außer <script> und <link> vom Framework ignoriert werden.

Schritt 3: Hinzufügen von Widget-JavaScript

Um die Widgetfunktion zu implementieren, fügen Sie dieses Skript dem <head> Abschnitt Ihrer HTML-Datei hinzu:

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

Wichtige JavaScript-Komponenten

Funktion Zweck
VSS.init() Initialisiert die Kommunikation zwischen Widget und Azure DevOps
VSS.require() Lädt erforderliche SDK-Bibliotheken und Widget-Hilfsprogramme
VSS.register() Registriert Ihr Widget mit einem eindeutigen Bezeichner.
WidgetHelpers.IncludeWidgetStyles() Wendet die standardmäßige Azure DevOps-Formatierung an.
VSS.notifyLoadSucceeded() Benachrichtigt das Framework, dass das Laden erfolgreich abgeschlossen wurde.

Von Bedeutung

Der Name des Widgets in VSS.register() muss mit dem id in Ihrem Erweiterungsmanifest übereinstimmen (Schritt 5).

Schritt 4: Hinzufügen von Erweiterungsbildern

Erstellen Sie die erforderlichen Bilder für Ihre Erweiterung:

  • Erweiterungslogo: Bild mit 98x98 Pixeln logo.png im img Ordner
  • Widget-Katalogsymbol: Bild mit 98x98 Pixeln CatalogIcon.png in dem img Ordner
  • Widgetvorschau: Bild mit 330 x 160 Pixeln preview.png im img Ordner

Diese Bilder werden im Marketplace- und Widget-Katalog angezeigt, wenn Benutzer verfügbare Erweiterungen durchsuchen.

Schritt 5: Erstellen des Erweiterungsmanifests

Erstellen Sie vss-extension.json im Stammverzeichnis Ihres Projekts. Diese Datei definiert die Metadaten und Beiträge Ihrer Erweiterung:

{
    "manifestVersion": 1,
    "id": "azure-devops-extensions-myExtensions",
    "version": "1.0.0",
    "name": "My First Set of Widgets",
    "description": "Samples containing different widgets extending dashboards",
    "publisher": "fabrikam",
    "categories": ["Azure Boards"],
    "targets": [
        {
            "id": "Microsoft.VisualStudio.Services"
        }
    ],
    "icons": {
        "default": "img/logo.png"
    },
    "contributions": [
        {
            "id": "HelloWorldWidget",
            "type": "ms.vss-dashboards-web.widget",
            "targets": [
                "ms.vss-dashboards-web.widget-catalog"
            ],
            "properties": {
                "name": "Hello World Widget",
                "description": "My first widget",
                "catalogIconUrl": "img/CatalogIcon.png",
                "previewImageUrl": "img/preview.png",
                "uri": "hello-world.html",
                "supportedSizes": [
                    {
                        "rowSpan": 1,
                        "columnSpan": 2
                    }
                ],
                "supportedScopes": ["project_team"]
            }
        }
    ],
    "files": [
        {
            "path": "hello-world.html",
            "addressable": true
        },
        {
            "path": "sdk/scripts",
            "addressable": true
        },
        {
            "path": "img",
            "addressable": true
        }
    ]
}

Von Bedeutung

Ersetzen Sie "publisher": "fabrikam" durch Ihren tatsächlichen Herausgebernamen. Erfahren Sie, wie Sie einen Herausgeber erstellen.

Wesentliche Manifesteigenschaften

`Section` Zweck
Grundlegende Informationen Erweiterungsname, Version, Beschreibung und Herausgeber
Symbole Pfade zu den visuellen Ressourcen Ihrer Erweiterung
Beiträge Widgetdefinitionen einschließlich ID, Typ und Eigenschaften
Dateien Alle Dateien, die in das Erweiterungspaket eingeschlossen werden sollen

Vollständige Manifestdokumentation finden Sie in der Erweiterungsmanifestreferenz.

Schritt 6: Packen und Veröffentlichen Ihrer Erweiterung

Packen Sie Ihre Erweiterung, und veröffentlichen Sie sie im Visual Studio Marketplace.

Verpackungstool installieren

npm i -g tfx-cli

Erstellen Sie Ihr Erweiterungspaket

Führen Sie den folgenden Befehl in Ihrem Projektverzeichnis aus:

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

Mit dieser Aktion wird eine .vsix Datei erstellt, die die paketierte Erweiterung enthält.

Einrichten eines Herausgebers

  1. Wechseln Sie zum Visual Studio Marketplace-Veröffentlichungsportal.
  2. Melden Sie sich an, und erstellen Sie einen Herausgeber, wenn Sie keinen haben.
  3. Wählen Sie einen eindeutigen Herausgeberbezeichner (verwendet in Ihrer Manifestdatei) aus.
  4. Aktualisieren Sie Ihr vss-extension.json, um Ihren Herausgebernamen anstelle von "fabrikam" zu verwenden.

Hochladen Ihrer Erweiterung

  1. Wählen Sie im Veröffentlichungsportal " Neue Erweiterung hochladen" aus.
  2. Wählen Sie Ihre .vsix Datei aus, und laden Sie sie hoch.
  3. Teilen Sie die Erweiterung mit Ihrer Azure DevOps-Organisation.

Alternativ können Sie die Befehlszeile verwenden:

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

Tipp

Verwenden Sie --rev-version, um die Versionsnummer automatisch zu erhöhen, wenn Sie eine vorhandene Erweiterung aktualisieren.

Schritt 7: Installieren und Testen Ihres Widgets

Zum Testen fügen Sie Ihr Widget zu einem Dashboard hinzu:

  1. Wechseln Sie zu Ihrem Azure DevOps-Projekt: https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Wechseln Sie zu Übersicht>Dashboards.
  3. Wählen Sie "Widget hinzufügen" aus.
  4. Suchen Sie Ihr Widget im Katalog, und wählen Sie "Hinzufügen" aus.

Ihr Widget "Hello World" wird auf dem Dashboard angezeigt und zeigt den von Ihnen konfigurierten Text an.

Nächster Schritt: Fahren Sie mit Teil 2 fort, um zu erfahren, wie Sie Azure DevOps-REST-APIs in Ihr Widget integrieren.

Teil 2: Hallo Welt mit der Azure DevOps-REST-API

Erweitern Sie Ihr Widget, um mit Azure DevOps-Daten mithilfe von REST-APIs zu interagieren. In diesem Beispiel wird veranschaulicht, wie Abfrageinformationen abgerufen und dynamisch in Ihrem Widget angezeigt werden.

Verwenden Sie in diesem Teil die REST-API für die Arbeitsaufgabenverfolgung , um Informationen zu einer vorhandenen Abfrage abzurufen und die Abfragedetails unter dem Text "Hello World" anzuzeigen.

Screenshot des Übersichtsdashboards mit einem Beispiel-Widget mit der REST-API für WorkItemTracking.

Schritt 1: Erstellen der erweiterten HTML-Datei

Erstellen Sie eine neue Widgetdatei, die auf dem vorherigen Beispiel basiert. Kopieren Sie hello-world.html und benennen Sie es in hello-world2.html um. Ihre Projektstruktur umfasst jetzt Folgendes:

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

Aktualisieren der HTML-Struktur des Widgets

Nehmen Sie diese Änderungen an hello-world2.html vor:

  1. Fügen Sie einen Container für Abfragedaten hinzu: Fügen Sie ein neues <div> Element zum Anzeigen von Abfrageinformationen hinzu.
  2. Aktualisieren Sie den Widgetbezeichner: Ändern Sie den Widgetnamen von HelloWorldWidget zu HelloWorldWidget2 "Zur eindeutigen Identifikation".
<!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>

Schritt 2: Konfigurieren von API-Zugriffsberechtigungen

Konfigurieren Sie vor dem Ausführen von REST-API-Aufrufen die erforderlichen Berechtigungen in Ihrem Erweiterungsmanifest.

Hinzufügen des Arbeitsumfangs

Die vso.work scope grants read-only access to work items and queries. Fügen Sie diesen Reservierungsumfang Ihrem vss-extension.json zu:

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

Vollständiges Manifestbeispiel

Strukturieren Sie es wie folgt für ein vollständiges Manifest mit anderen Eigenschaften:

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

Von Bedeutung

Bereichseinschränkungen: Das Hinzufügen oder Ändern von Bereichen nach der Veröffentlichung wird nicht unterstützt. Wenn Sie Ihre Erweiterung bereits veröffentlicht haben, müssen Sie sie zuerst aus dem Marketplace entfernen. Wechseln Sie zum Visual Studio Marketplace-Veröffentlichungsportal, suchen Sie Ihre Erweiterung, und wählen Sie "Entfernen" aus.

Schritt 3: Implementieren der REST-API-Integration

Azure DevOps stellt JavaScript-REST-Clientbibliotheken über das SDK bereit. Diese Bibliotheken umschließen AJAX-Aufrufe und ordnen API-Antworten verwendbaren Objekten zu.

Aktualisieren des Widgets JavaScript

Ersetzen Sie den VSS.require Aufruf in Ihrem hello-world2.html, um den REST-Client für die Aufgabenverfolgung einzuschließen.

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

Wichtige Implementierungsdetails

Komponente Zweck
WorkItemTrackingRestClient.getClient() Ruft eine Instanz des REST-Clients für die Arbeitsaufgabenverfolgung ab
getQuery() Ruft Abfrageinformationen ab, die in eine Zusage eingeschlossen sind
WidgetStatusHelper.Failure() Stellt eine konsistente Fehlerbehandlung für Widgetfehler bereit.
projectId Aktueller Projektkontext erforderlich für API-Aufrufe

Tipp

Benutzerdefinierte Abfragepfade: Wenn Sie keine "Feedback"-Abfrage in "Freigegebene Abfragen" haben, ersetzen Sie "Shared Queries/Feedback" den Pfad zu einer Abfrage, die in Ihrem Projekt vorhanden ist.

Schritt 4: Anzeigen von API-Antwortdaten

Rendern Sie die Abfrageinformationen in Ihrem Widget, indem Sie die REST-API-Antwort verarbeiten.

Hinzufügen des Renderns von Abfragedaten

Ersetzen Sie den // Process query data Kommentar durch diese Implementierung:

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

Die getQuery() Methode gibt ein Contracts.QueryHierarchyItem Objekt mit Eigenschaften für Abfragemetadaten zurück. In diesem Beispiel werden drei wichtige Informationen unterhalb des Texts "Hello World" angezeigt.

Vollständiges Arbeitsbeispiel

Die endgültige hello-world2.html Datei sollte wie folgt aussehen:

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

Schritt 5: Aktualisieren des Erweiterungsmanifests

Um es im Widgetkatalog verfügbar zu machen, fügen Sie Ihr neues Widget dem Erweiterungsmanifest hinzu.

Hinzufügen des zweiten Widgetbeitrags

Aktualisieren Sie vss-extension.json , um Ihr REST-API-fähiges Widget einzuschließen. Fügen Sie diesen Beitrag in das Array contributions hinzu.

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

Tipp

Vorschaubild: Erstellen Sie ein preview2.png Bild (330 x 160 Pixel), und platzieren Sie es im img Ordner, um Benutzern anzuzeigen, wie Ihr Widget im Katalog aussieht.

Schritt 6: Packen, Veröffentlichen und Freigeben

Verpacken, veröffentlichen und teilen Sie Ihre Erweiterung. Wenn Sie die Erweiterung bereits veröffentlicht haben, können Sie sie direkt im Marketplace umpacken und aktualisieren.

Schritt 7: Testen Des REST-API-Widgets

Um die REST-API-Integration in Aktion anzuzeigen, fügen Sie das neue Widget zu Ihrem Dashboard hinzu:

  1. Wechseln Sie zu Ihrem Azure DevOps-Projekt: https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Wählen Sie Übersicht>Dashboards aus.
  3. Wählen Sie "Widget hinzufügen" aus.
  4. Suchen Sie "Hello World Widget 2 (mit API)", und wählen Sie "Hinzufügen" aus.

Ihr erweitertes Widget zeigt sowohl den Text "Hello World" als auch Liveabfrageinformationen aus Ihrem Azure DevOps-Projekt an.

Nächste Schritte: Fahren Sie mit Teil 3 fort, um Konfigurationsoptionen hinzuzufügen, mit denen Benutzer anpassen können, welche Abfrage angezeigt werden soll.

Teil 3: Konfigurieren von Hallo Welt

Bauen Sie auf Teil 2 auf, indem Sie Ihrem Widget Benutzerkonfigurationsfunktionen hinzufügen. Anstatt den Abfragepfad hartcodieren zu müssen, erstellen Sie eine Konfigurationsschnittstelle, über die Benutzer auswählen können, welche Abfrage angezeigt werden soll, mit Livevorschaufunktionen.

In diesem Teil wird veranschaulicht, wie konfigurierbare Widgets erstellt werden, die Benutzer an ihre spezifischen Anforderungen anpassen können, während während der Konfiguration Echtzeitfeedback bereitgestellt wird.

Screenshot der Livevorschau des Widgets im Übersichtsdashboard, basierend auf Änderungen.

Schritt 1: Erstellen von Konfigurationsdateien

Widgetkonfigurationen teilen viele Ähnlichkeiten mit Widgets selbst – beide verwenden dieselben SDK-, HTML-Struktur- und JavaScript-Muster, dienen jedoch unterschiedlichen Zwecken innerhalb des Erweiterungsframeworks.

Einrichten der Projektstruktur

Erstellen Sie zwei neue Dateien, um die Widgetkonfiguration zu unterstützen:

  1. Kopieren Sie hello-world2.html und benennen Sie es in hello-world3.html um, Ihr konfigurierbares Widget.
  2. Erstellen Sie eine neue Datei namens configuration.html, die die Konfigurationsschnittstelle behandelt.

Ihre Projektstruktur umfasst jetzt Folgendes:

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

Erstellen der Konfigurationsschnittstelle

Fügen Sie diese HTML-Struktur zu configuration.html hinzu, die eine Dropdownauswahl für die Auswahl von Abfragen erstellt.

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

Schritt 2: Implementieren von Konfigurations-JavaScript

Configuration JavaScript folgt dem gleichen Initialisierungsmuster wie Widgets, implementiert jedoch den IWidgetConfiguration Vertrag anstelle des Basisvertrags IWidget .

Hinzufügen von Konfigurationslogik

Fügen Sie dieses Skript in den <head> Abschnitt von 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>

Konfigurationsvertragsdetails

Für den IWidgetConfiguration Vertrag sind die folgenden wichtigen Funktionen erforderlich:

Funktion Zweck Bei Aufruf
load() Initialisieren der Konfigurations-UI mit vorhandenen Einstellungen Wenn das Konfigurationsdialogfeld geöffnet wird
onSave() Serialisieren von Benutzereingaben und Überprüfen von Einstellungen Wenn der Benutzer "Speichern" auswählt

Tipp

Daten serialisieren: In diesem Beispiel wird JSON zum Serialisieren von Einstellungen verwendet. Das Widget greift auf diese Einstellungen über widgetSettings.customSettings.data zu und muss sie korrekt deserialisieren.

Schritt 3: Aktivieren der Livevorschaufunktion

Mit der Livevorschau können Benutzer Widgetänderungen sofort sehen, während sie Konfigurationseinstellungen ändern und sofortiges Feedback vor dem Speichern bereitstellen.

Implementieren von Änderungsbenachrichtigungen

Um die Livevorschau zu aktivieren, fügen Sie diesen Ereignishandler innerhalb der load Funktion hinzu:

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

Vollständige Konfigurationsdatei

Ihr Endgültiges configuration.html sollte wie folgt aussehen:

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

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

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

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

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

Von Bedeutung

Schaltfläche "Speichern" aktivieren: Das Framework erfordert mindestens eine Konfigurationsänderungsbenachrichtigung, um die Schaltfläche " Speichern " zu aktivieren. Der Änderungsereignishandler stellt sicher, dass diese Aktion auftritt, wenn Benutzer eine Option auswählen.

Schritt 4: Konfigurieren des Widgets

Transformieren Sie Ihr Widget aus Teil 2, um Konfigurationsdaten anstelle hartcodierter Werte zu verwenden. Dieser Schritt erfordert die Implementierung des IConfigurableWidget Vertrags.

Aktualisieren der Widgetregistrierung

Nehmen Sie in hello-world3.html folgende Änderungen vor:

  1. Widget-ID aktualisieren: Von HelloWorldWidget2 zu HelloWorldWidget3.
  2. Neuladefunktion hinzufügen: Implementieren Sie den IConfigurableWidget Vertrag.
return {
    load: function (widgetSettings) {
        // Set your title
        var $title = $('h2.title');
        $title.text('Hello World');

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

Behandeln von Konfigurationsdaten

Aktualisieren Sie die getQueryInfo Funktion so, dass Konfigurationseinstellungen anstelle hartcodierter Abfragepfade verwendet werden:

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

Unterschiede beim Widgetlebenszyklus

Funktion Zweck Nutzungsrichtlinien
load() Anfängliches Widgetrendering und einmaliges Setup Schwere Vorgänge, Ressourceninitialisierung
reload() Widget mit neuer Konfiguration aktualisieren Einfache Updates, Datenaktualisierung

Tipp

Leistungsoptimierung: Wird load() für teure Vorgänge verwendet, die nur einmal ausgeführt werden müssen, und reload() für schnelle Updates, wenn sich die Konfiguration ändert.

(Optional) Zum Hinzufügen einer Lightbox-Komponente für detaillierte Informationen

Dashboard-Widgets haben begrenzten Platz und machen es schwierig, umfassende Informationen anzuzeigen. Eine Lightbox bietet eine elegante Lösung, indem detaillierte Daten in einer modalen Überlagerung angezeigt werden, ohne vom Dashboard weg zu navigieren.

Warum eine Lightbox in Widgets verwenden?

Nutzen Beschreibung
Platzeffizienz Halten Sie widget kompakt, während Sie detaillierte Ansichten anbieten
Benutzererfahrung Den Dashboard-Kontext beibehalten, während mehr Informationen angezeigt werden.
Schrittweise Offenlegung Zusammenfassungsdaten im Widget anzeigen, Details bei Bedarf
Dynamisches Design Anpassen an verschiedene Bildschirmgrößen und Widgetkonfigurationen

Implementieren von klickbaren Elementen

Aktualisieren Sie das Rendern der Abfragedaten so, dass sie klickbare Elemente enthält, die das Lightbox-Element auslösen:

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

Erstellen der Lightbox-Funktionalität

Fügen Sie diese Lightbox-Implementierung zu Ihrem Widget JavaScript hinzu:

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

Hinzufügen von Lightbox-Stil

Fügen Sie CSS-Formatvorlagen für das Lightbox-Element in Ihren HTML-Abschnitt <head> des Widgets ein:

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

Erweiterte Widgetimplementierung

Ihr vollständiges erweitertes Widget mit Lightbox-Funktionalität:

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

Überlegungen zur Barrierefreiheit: Stellen Sie sicher, dass Ihre Lightbox tastaturbedienbar ist und geeignete Bezeichnungen für Bildschirmleser enthält. Testen Sie die integrierten Barrierefreiheitsfeatures von Azure DevOps.

Von Bedeutung

Leistung: Lightboxes sollten schnell geladen werden. Erwägen Sie, detaillierte Daten erst beim Öffnen der Lightbox nachzuladen, anstatt alles im Voraus abzurufen.

Schritt 5: Konfigurieren des Erweiterungsmanifests

Registrieren Sie sowohl das konfigurierbare Widget als auch die Konfigurationsschnittstelle in Ihrem Erweiterungsmanifest.

Hinzufügen von Widget- und Konfigurationsbeiträgen

Aktualisierung von vss-extension.json, um zwei neue Beiträge einzuschließen:

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

Konfigurationsbeitragsanforderungen

Eigenschaft Zweck Erforderlicher Wert
type Identifiziert den Beitrag als Widgetkonfiguration ms.vss-dashboards-web.widget-configuration
targets Wo die Konfiguration angezeigt wird ms.vss-dashboards-web.widget-configuration
uri Pfad zur HTML-Konfigurationsdatei Pfad der Konfigurationsdatei

Widgetadressierungsmuster

Für konfigurierbare Widgets muss das targets Array einen Verweis auf die Konfiguration enthalten:

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

Warnung

Sichtbarkeit der Konfigurationsschaltfläche: Wenn das Widget nicht ordnungsgemäß auf seinen Konfigurationsbeitrag ausgerichtet ist, wird die Schaltfläche " Konfigurieren " nicht angezeigt. Überprüfen Sie, ob die Herausgeber- und Erweiterungsnamen ihrem Manifest exakt entsprechen.

Schritt 6: Packen, Veröffentlichen und Freigeben

Stellen Sie Ihre erweiterte Erweiterung mit Konfigurationsfunktionen bereit.

Wenn es sich um Ihre erste Publikation handelt, folgen Sie Schritt 6: Packen, Veröffentlichen und Freigeben. Für vorhandene Erweiterungen können Sie diese direkt im Marketplace umverpacken und aktualisieren.

Schritt 7: Testen des konfigurierbaren Widgets

Erleben Sie den vollständigen Konfigurationsworkflow, indem Sie Ihr Widget hinzufügen und konfigurieren.

Fügen Sie das Widget zum Ihrem Dashboard hinzu.

  1. Gehe zu https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Wechseln Sie zu Übersicht>Dashboards.
  3. Wählen Sie "Widget hinzufügen" aus.
  4. Suchen Sie "Hello World Widget 3 (mit Konfiguration)", und wählen Sie "Hinzufügen" aus.

Eine Konfigurationsaufforderung wird angezeigt, da für das Widget Setup erforderlich ist:

Screenshot des Übersichtsdashboards mit einem Beispiel-Widget aus dem Katalog.

Konfigurieren des Widgets

Access-Konfiguration über eine der beiden Methoden:

  • Widget-Menü: Bewegen Sie den Mauszeiger über das Widget, wählen Sie die Ellipse (⋯) und dann Konfigurieren
  • Dashboard-Bearbeitungsmodus: Wählen Sie " Bearbeiten " im Dashboard und dann die Schaltfläche "Konfigurieren" im Widget aus.

Der Konfigurationsbereich wird mit einer Livevorschau im Zentrum geöffnet. Wählen Sie eine Abfrage aus der Dropdownliste aus, um sofortige Updates anzuzeigen, und wählen Sie dann "Speichern" aus, um Ihre Änderungen anzuwenden.

Schritt 8: Hinzufügen erweiterter Konfigurationsoptionen

Erweitern Sie Ihr Widget mit mehr integrierten Konfigurationsfeatures wie benutzerdefinierten Namen und Größen.

Aktivieren der Namens- und Größenkonfiguration

Azure DevOps bietet zwei konfigurierbare Features sofort einsatzbereit:

Merkmal Manifesteigenschaft Zweck
Benutzerdefinierte Namen isNameConfigurable: true Benutzer können den Standard-Widgetnamen außer Kraft setzen.
Mehrere Größen Mehrere supportedSizes Einträge Benutzer können die Größe von Widgets ändern.

Beispiel für ein erweitertes Manifest

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

Konfigurierte Namen anzeigen

Um benutzerdefinierte Widgetnamen anzuzeigen, aktualisieren Sie Ihr Widget, um widgetSettings.name zu verwenden.

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

Nachdem Sie Die Erweiterung aktualisiert haben, können Sie sowohl den Widgetnamen als auch die Größe konfigurieren:

Screenshot, der zeigt, wo der Widgetname und die Größe konfiguriert werden können.

Neuverpacken und aktualisieren Sie Ihre Erweiterung, um diese erweiterten Konfigurationsoptionen zu aktivieren.

Glückwunsch! Sie haben ein vollständiges, konfigurierbares Azure DevOps-Dashboard-Widget mit Livevorschaufunktionen und Benutzeranpassungsoptionen erstellt.