Delen via


Een dashboardwidget toevoegen

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

Widgets op een dashboard worden geïmplementeerd als bijdragen in het extensieframework. Eén extensie kan meerdere bijdragen hebben. Meer informatie over het maken van een extensie met meerdere widgets als bijdragen.

Dit artikel is onderverdeeld in drie delen, elk voortbouwend op het vorige. U begint met een eenvoudige widget en eindigt met een uitgebreide widget.

Aanbeveling

Bekijk onze nieuwste documentatie over uitbreidingsontwikkeling met behulp van de Azure DevOps Extension SDK.

Vereisten

Voorwaarde Beschrijving
Programmeerkennis JavaScript-, HTML- en CSS-kennis voor widgetontwikkeling
Azure DevOps-organisatie Een organisatie maken
Teksteditor We gebruiken Visual Studio Code voor zelfstudies
Node.js Nieuwste versie van Node.js
Platformoverschrijdende CLI tfx-cli om extensies te verpakken
Installeren met behulp van: npm i -g tfx-cli
Projectmap Basismap met de volgende structuur na het voltooien van de handleiding:

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

Overzicht van zelfstudies

In deze zelfstudie leert u widgetontwikkeling via drie progressieve voorbeelden:

Onderdeel Focus Wat u leert
Deel 1: Hallo wereld Basiswidget maken Een widget maken waarmee tekst wordt weergegeven
Deel 2: REST API-integratie Azure DevOps-API-aanroepen REST API-functionaliteit toevoegen om gegevens op te halen en weer te geven
Deel 3: Widgetconfiguratie Aanpassing van gebruikers Configuratieopties voor uw widget implementeren

Aanbeveling

Sla de zelfstudie over: Download de volledige voorbeeldextensie, ga naar de widgets map en ga naar stap 6 om drie kant-en-klare voorbeeldwidgets te publiceren.

Bekijk voordat u begint de basiswidgetstijlen en structurele richtlijnen die we bieden.

Deel 1: Hallo wereld

Maak een basiswidget die 'Hallo wereld' weergeeft met behulp van JavaScript. Deze basis demonstreert de basisconcepten voor widgetontwikkeling.

Schermopname van het dashboard Overzicht met een voorbeeldwidget.

Stap 1: de client-SDK installeren

Met de VSS SDK kan uw widget communiceren met Azure DevOps. Installeer deze met behulp van npm:

npm install vss-web-extension-sdk

Kopieer het VSS.SDK.min.js bestand van vss-web-extension-sdk/lib naar uw home/sdk/scripts map.

Zie de GitHub-pagina client-SDK voor meer SDK-documentatie.

Stap 2: de HTML-structuur maken

Maak hello-world.html in uw projectmap. Dit bestand biedt de indeling en verwijzingen naar de vereiste scripts van de widget.

<!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 worden uitgevoerd in iframes, dus de meeste HTML-hoofdelementen behalve <script> en <link> worden genegeerd door het framework.

Stap 3: Widget JavaScript toevoegen

Als u de widgetfunctionaliteit wilt implementeren, voegt u dit script toe aan de <head> sectie van uw HTML-bestand:

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

Belangrijke JavaScript-onderdelen

Functie Doel
VSS.init() Initialiseert de communicatie tussen widget en Azure DevOps
VSS.require() Het laden van de vereiste SDK-bibliotheken en widgethelpers
VSS.register() Registreert uw widget met een unieke id
WidgetHelpers.IncludeWidgetStyles() Standaardstijl van Azure DevOps wordt toegepast
VSS.notifyLoadSucceeded() Hiermee wordt het framework geïnformeerd dat het laden succesvol is voltooid.

Belangrijk

De widgetnaam in VSS.register() moet overeenkomen met de id in uw extensiemanifest (stap 5).

Stap 4: Extensie-installatiekopieën toevoegen

Maak de vereiste installatiekopieën voor uw extensie:

  • Extensielogo: 98x98 pixel afbeelding met de naam logo.png in de img map
  • Widgetcataloguspictogram: afbeelding van 98x98 pixel met de naam CatalogIcon.png in de img map
  • Widgetvoorbeeld: afbeelding van 330x160 pixel met de naam preview.png in de img map

Deze afbeeldingen worden weergegeven in de Marketplace- en widgetcatalogus wanneer gebruikers door beschikbare extensies bladeren.

Stap 5: Het extensiemanifest maken

Maak vss-extension.json in de hoofdmap van uw project. Dit bestand definieert de metagegevens en bijdragen van uw extensie:

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

Belangrijk

Vervang door "publisher": "fabrikam" de werkelijke naam van de uitgever. Leer hoe je een uitgever aanmaakt.

Essentiële manifesteigenschappen

Afdeling Doel
Basisinformatie Extensienaam, versie, beschrijving en uitgever
pictogrammen Paden naar de visuele assets van uw extensie
Bijdragen Widgetdefinities, waaronder id, type en eigenschappen
bestanden Alle bestanden die moeten worden opgenomen in het extensiepakket

Zie referentiemateriaal voor uitbreidingsmanifesten voor volledige documentatie over manifesten.

Stap 6: Uw extensie inpakken en publiceren

Pak uw extensie in en publiceer deze naar Visual Studio Marketplace.

Het pakketprogramma installeren

npm i -g tfx-cli

Uw uitbreidingspakket maken

Voer vanuit uw projectmap het volgende uit:

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

Met deze actie maakt u een .vsix bestand met de verpakte extensie.

Een uitgever instellen

  1. Ga naar de Visual Studio Marketplace-publicatieportal.
  2. Meld u aan en maak een uitgever als u er nog geen hebt.
  3. Kies een unieke uitgever-id (gebruikt in uw manifestbestand).
  4. Werk uw vss-extension.json bij om uw uitgeversnaam te gebruiken in plaats van "fabrikam."

Uw extensie uploaden

  1. Selecteer in de publicatieportal de optie Nieuwe extensie uploaden.
  2. Kies uw .vsix bestand en upload het.
  3. Deel de extensie met uw Azure DevOps-organisatie.

U kunt ook de opdrachtregel gebruiken:

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

Aanbeveling

Hiermee --rev-version kunt u het versienummer automatisch verhogen bij het bijwerken van een bestaande extensie.

Stap 7: Uw widget installeren en testen

Als u wilt testen, voegt u uw widget toe aan een dashboard:

  1. Ga naar uw Azure DevOps-project: https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Ga naar Overzicht>Dashboards.
  3. Selecteer Een widget toevoegen.
  4. Zoek uw widget in de catalogus en selecteer Toevoegen.

De widget Hallo wereld wordt weergegeven op het dashboard, met de tekst die u hebt geconfigureerd.

Volgende stap: Ga verder met deel 2 om te leren hoe u Azure DevOps REST API's integreert in uw widget.

Deel 2: Hallo wereld met Azure DevOps REST API

Breid uw widget uit om te communiceren met Azure DevOps-gegevens met behulp van REST API's. In dit voorbeeld ziet u hoe u querygegevens ophaalt en dynamisch weergeeft in uw widget.

In dit deel gebruikt u de REST API voor het bijhouden van werkitems om informatie over een bestaande query op te halen en de querygegevens weer te geven onder de tekst 'Hallo wereld'.

Schermopname van het overzichtsdashboard met een voorbeeldwidget met behulp van de REST API for WorkItemTracking.

Stap 1: het verbeterde HTML-bestand maken

Maak een nieuw widgetbestand dat voortbouwt op het vorige voorbeeld. Kopieer hello-world.html en wijzig de naam ervan in hello-world2.html. Uw projectstructuur bevat nu:

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

De HTML-structuur van de widget bijwerken

Breng deze wijzigingen aan in hello-world2.html:

  1. Voeg een container toe voor querygegevens: Voeg een nieuw <div> element toe om querygegevens weer te geven.
  2. Werk de widget-id bij: wijzig de widgetnaam van HelloWorldWidget in HelloWorldWidget2 voor unieke identificatie.
<!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>

Stap 2: API-toegangsmachtigingen configureren

Voordat u REST API-aanroepen maakt, configureert u de vereiste machtigingen in uw extensiemanifest.

Het werkbereik toevoegen

Het vso.work bereik verleent alleen-lezentoegang tot werkitems en query's. Voeg dit bereik toe aan uw vss-extension.json:

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

Voorbeeld van het volledige manifest

Voor een volledig manifest met andere eigenschappen structureert u het als volgt:

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

Belangrijk

Bereikbeperkingen: Het toevoegen of wijzigen van scopes na publicatie wordt niet ondersteund. Als u uw extensie al hebt gepubliceerd, moet u deze eerst verwijderen uit Marketplace. Ga naar de Visual Studio Marketplace-publicatieportal, zoek uw extensie en selecteer Verwijderen.

Stap 3: REST API-integratie implementeren

Azure DevOps biedt JavaScript REST-clientbibliotheken via de SDK. Deze bibliotheken verpakken AJAX-aanroepen en wijzen API-antwoorden toe aan bruikbare objecten.

De widget JavaScript bijwerken

Vervang de VSS.require aanroep in uw hello-world2.html om de Work Item Tracking REST-client te omvatten:

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

Belangrijke implementatiedetails

Onderdeel Doel
WorkItemTrackingRestClient.getClient() Hiermee haalt u een exemplaar op van de REST-client voor het bijhouden van werkitems
getQuery() Haalt querygegevens op die zijn verpakt in een belofte
WidgetStatusHelper.Failure() Biedt consistente foutafhandeling voor widgetfouten
projectId Huidige projectcontext vereist voor API-aanroepen

Aanbeveling

Aangepaste querypaden: Als u geen "Feedback"-query in "Gedeelde query's" hebt, vervang "Shared Queries/Feedback" door het pad naar een query die in uw project bestaat.

Stap 4: API-antwoordgegevens weergeven

Geef de querygegevens in uw widget weer door het REST API-antwoord te verwerken.

Querygegevensweergave toevoegen

Vervang de // Process query data opmerking door deze implementatie:

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

De getQuery() methode retourneert een Contracts.QueryHierarchyItem object met eigenschappen voor querymetagegevens. In dit voorbeeld worden drie belangrijke stukjes informatie weergegeven onder de tekst 'Hallo wereld'.

Volledig werkvoorbeeld

Het uiteindelijke hello-world2.html bestand moet er als volgt uitzien:

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

Stap 5: Het extensiemanifest bijwerken

Als u deze beschikbaar wilt maken in de widgetcatalogus, voegt u uw nieuwe widget toe aan het extensiemanifest.

De tweede widgetbijdrage toevoegen

Werk vss-extension.json bij om uw widget dat is uitgerust met REST API op te nemen. Voeg deze bijdrage toe aan de contributions matrix:

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

Aanbeveling

Voorbeeldafbeelding: Maak een preview2.png afbeelding (330x160 pixels) en plaats deze in de img map om gebruikers te laten zien hoe uw widget eruitziet in de catalogus.

Stap 6: Verpakken, publiceren en delen

Uw extensie verpakken, publiceren en delen. Als u de extensie al hebt gepubliceerd, kunt u deze opnieuw verpakken en bijwerken in Marketplace.

Stap 7: Uw REST API-widget testen

Als u de INTEGRATIE van de REST API in actie wilt weergeven, voegt u de nieuwe widget toe aan uw dashboard:

  1. Ga naar uw Azure DevOps-project: https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Selecteer Overzicht>Dashboards.
  3. Selecteer Een widget toevoegen.
  4. Zoek 'Hello World Widget 2 (met API)' en selecteer Toevoegen.

In uw verbeterde widget worden zowel de tekst 'Hallo wereld' als de livequerygegevens van uw Azure DevOps-project weergegeven.

Volgende stappen: Ga verder met deel 3 om configuratieopties toe te voegen waarmee gebruikers kunnen aanpassen welke query moet worden weergegeven.

Deel 3: Hallo wereld configureren

Bouw voort op deel 2 door gebruikersconfiguratiemogelijkheden toe te voegen aan uw widget. In plaats van het querypad hard te coderen, maakt u een configuratie-interface waarmee gebruikers kunnen selecteren welke query moet worden weergegeven, met live preview-functionaliteit.

In dit deel ziet u hoe u configureerbare widgets maakt die gebruikers kunnen aanpassen aan hun specifieke behoeften en tegelijkertijd realtime feedback kunnen geven tijdens de configuratie.

Schermopname van livevoorbeeld van het overzichtsdashboard van de widget op basis van wijzigingen.

Stap 1: Configuratiebestanden maken

Widgetconfiguraties delen veel overeenkomsten met widgets zelf: beide gebruiken dezelfde SDK-, HTML-structuur en JavaScript-patronen, maar dienen verschillende doeleinden binnen het extensieframework.

De projectstructuur instellen

Maak twee nieuwe bestanden om de widgetconfiguratie te ondersteunen:

  1. Kopieer hello-world2.html en wijzig de naam ervan in hello-world3.html, uw configureerbare widget.
  2. Maak een nieuw bestand met de naam configuration.html, waarmee de configuratie-interface wordt verwerkt.

Uw projectstructuur bevat nu:

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

De configuratie-interface maken

Voeg deze HTML-structuur toe aan configuration.html, waarmee een vervolgkeuzelijst wordt gemaakt voor het kiezen van query's:

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

Stap 2: Configuratie van JavaScript implementeren

De configuratie van JavaScript volgt hetzelfde initialisatiepatroon als widgets, maar implementeert het IWidgetConfiguration contract in plaats van het basiscontract IWidget .

Configuratielogica toevoegen

Voeg dit script in de <head> sectie van 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>

Details van configuratiecontract

Voor het IWidgetConfiguration contract zijn de volgende belangrijke functies vereist:

Functie Doel Wanneer aangeroepen
load() De gebruikersinterface van de configuratie initialiseren met bestaande instellingen Wanneer het configuratiedialoogvenster wordt geopend
onSave() Gebruikersinvoer serialiseren en instellingen valideren Wanneer de gebruiker Opslaan selecteert

Aanbeveling

Gegevensserialisatie: in dit voorbeeld wordt JSON gebruikt om instellingen te serialiseren. De widget heeft toegang tot deze instellingen via widgetSettings.customSettings.data en moet ze dienovereenkomstig deserialiseren.

Stap 3: Live preview-functionaliteit inschakelen

Met livevoorbeeld kunnen gebruikers widgetwijzigingen onmiddellijk zien wanneer ze configuratie-instellingen wijzigen, zodat ze direct feedback kunnen geven voordat ze deze opslaan.

Wijzigingsmeldingen implementeren

Als u livevoorbeeld wilt inschakelen, voegt u deze gebeurtenis-handler toe binnen de load functie:

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

Configuratiebestand voltooien

Uw laatste configuration.html moet er als volgt uitzien:

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

Belangrijk

Knop Opslaan inschakelen: Voor het framework is ten minste één melding voor configuratiewijzigingen vereist om de knop Opslaan in te schakelen. De gebeurtenis-handler voor wijzigingen zorgt ervoor dat deze actie plaatsvindt wanneer gebruikers een optie selecteren.

Stap 4: de widget configureerbaar maken

Transformeer uw widget van deel 2 om configuratiegegevens te gebruiken in plaats van vastgelegde waarden. Voor deze stap is het implementeren van het IConfigurableWidget contract vereist.

Widgetregistratie bijwerken

Breng hello-world3.htmlde volgende wijzigingen aan:

  1. Widget-id bijwerken: van naar HelloWorldWidget2HelloWorldWidget3.
  2. Voeg de functie voor opnieuw laden toe: Implementeer het IConfigurableWidget contract.
return {
    load: function (widgetSettings) {
        // Set your title
        var $title = $('h2.title');
        $title.text('Hello World');

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

Configuratiegegevens verwerken

Werk de getQueryInfo functie bij om configuratie-instellingen te gebruiken in plaats van in code vastgelegde querypaden:

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

Verschillen in levenscyclus van widget

Functie Doel Gebruiksrichtlijnen
load() Eerste widgetweergave en eenmalige installatie Zware bewerkingen, resource-initialisatie
reload() Widget bijwerken met nieuwe configuratie Lichtgewicht updates, gegevensvernieuwing

Aanbeveling

Optimalisatie van prestaties: gebruik load() deze functie voor dure bewerkingen die slechts één keer hoeven te worden uitgevoerd en reload() voor snelle updates wanneer de configuratie verandert.

(Optioneel) Een lightbox toevoegen voor gedetailleerde informatie

Dashboardwidgets hebben beperkte ruimte, waardoor het lastig is om uitgebreide informatie weer te geven. Een lightbox biedt een elegante oplossing door gedetailleerde gegevens weer te geven in een modale overlay zonder weg te navigeren van het dashboard.

Waarom een lightbox gebruiken in widgets?

Voordeel Beschrijving
Ruimte-efficiëntie Widget compact houden terwijl u gedetailleerde weergaven biedt
Gebruikerservaring Dashboardcontext onderhouden terwijl meer informatie wordt weergegeven
progressieve openbaarmaking Samenvattingsgegevens weergeven in widget, details op aanvraag
Responsief ontwerp Aanpassen aan verschillende schermgrootten en widgetconfiguraties

Klikbare elementen implementeren

Werk de rendering van uw querygegevens bij om klikbare elementen op te nemen die het lightbox activeren:

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

De lightbox-functionaliteit maken

Voeg deze lightbox-implementatie toe aan uw widget JavaScript:

function showQueryDetails(query) {
    // Create lightbox overlay
    var $overlay = $('<div class="lightbox-overlay">');
    var $lightbox = $('<div class="lightbox-content">');
    
    // Add close button
    var $closeBtn = $('<button class="lightbox-close">&times;</button>');
    $closeBtn.on('click', function() {
        $overlay.remove();
    });
    
    // Create detailed content
    var $content = $('<div class="query-details">');
    $content.append($('<h3>').text(query.name || 'Query Details'));
    $content.append($('<p>').html('<strong>ID:</strong> ' + query.id));
    $content.append($('<p>').html('<strong>Path:</strong> ' + query.path));
    $content.append($('<p>').html('<strong>Created:</strong> ' + (query.createdDate ? new Date(query.createdDate).toLocaleDateString() : 'Unknown')));
    $content.append($('<p>').html('<strong>Modified:</strong> ' + (query.lastModifiedDate ? new Date(query.lastModifiedDate).toLocaleDateString() : 'Unknown')));
    $content.append($('<p>').html('<strong>Created By:</strong> ' + (query.createdBy ? query.createdBy.displayName : 'Unknown')));
    $content.append($('<p>').html('<strong>Modified By:</strong> ' + (query.lastModifiedBy ? query.lastModifiedBy.displayName : 'Unknown')));
    
    if (query.queryType) {
        $content.append($('<p>').html('<strong>Type:</strong> ' + query.queryType));
    }
    
    // Assemble lightbox
    $lightbox.append($closeBtn);
    $lightbox.append($content);
    $overlay.append($lightbox);
    
    // Add to document and show
    $('body').append($overlay);
    
    // Close on overlay click
    $overlay.on('click', function(e) {
        if (e.target === $overlay[0]) {
            $overlay.remove();
        }
    });
    
    // Close on Escape key
    $(document).on('keydown.lightbox', function(e) {
        if (e.keyCode === 27) { // Escape key
            $overlay.remove();
            $(document).off('keydown.lightbox');
        }
    });
}

Lightbox-stijl toevoegen

Neem CSS-stijlen voor het lightbox op in de HTML-sectie <head> van uw widget:

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

Verbeterde widget-implementatie

Uw volledige verbeterde widget met lightbox-functionaliteit:

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

Overwegingen voor toegankelijkheid: Zorg ervoor dat uw lightbox toegankelijk is en de juiste labels voor schermlezers bevat. Test met de ingebouwde toegankelijkheidsfuncties van Azure DevOps.

Belangrijk

Prestaties: Lightboxes zouden snel moeten laden. Overweeg alleen gedetailleerde gegevens te laden wanneer de lightbox wordt geopend, in plaats van alles vooraf op te halen.

Stap 5: Het extensiemanifest configureren

Registreer zowel de configureerbare widget als de configuratie-interface in uw extensiemanifest.

Widget- en configuratiebijdragen toevoegen

Werk vss-extension.json bij om twee nieuwe bijdragen op te nemen:

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

Vereisten voor configuratiebijdrage

Eigenschappen Doel Vereiste waarde
type Identificeert bijdrage als widgetconfiguratie ms.vss-dashboards-web.widget-configuration
targets Waar de configuratie wordt weergegeven ms.vss-dashboards-web.widget-configuration
uri Pad naar HTML-configuratiebestand Het pad naar het configuratiebestand

Widgetdoelpatroon

Voor configureerbare widgets moet de targets matrix een verwijzing naar de configuratie bevatten:

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

Waarschuwing

Zichtbaarheid van de configuratieknop: als de widget niet juist is gericht op de configuratiebijdrage, wordt de knop Configureren niet weergegeven. Controleer of de uitgevers- en extensienamen exact overeenkomen met uw manifest.

Stap 6: Verpakken, publiceren en delen

Implementeer uw verbeterde extensie met configuratiemogelijkheden.

Als dit uw eerste publicatie is, volgt u stap 6: Verpakken, publiceren en delen. Voor bestaande extensies kunt u deze rechtstreeks in Marketplace opnieuw verpakken en bijwerken.

Stap 7: De configureerbare widget testen

Ervaar de volledige configuratiewerkstroom door uw widget toe te voegen en te configureren.

De widget toevoegen aan uw dashboard

  1. Ga naar https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Ga naar Overzicht>Dashboards.
  3. Selecteer Een widget toevoegen.
  4. Zoek 'Hallo wereldwidget 3 (met configuratie)' en selecteer Toevoegen.

Er wordt een configuratieprompt weergegeven omdat de widget setup vereist:

Schermopname van overzichtsdashboard met een voorbeeldwidget uit de catalogus.

De widget configureren

Toegangsconfiguratie via een van beide methoden:

  • Widgetmenu: Beweeg de muisaanwijzer over de widget, selecteer het beletselteken (⋯) en configureer
  • Dashboardbewerkingsmodus: Selecteer Bewerken op het dashboard en vervolgens de knop Configureren in de widget

Het configuratievenster opent met een live-voorbeeld in het midden. Selecteer een query in de vervolgkeuzelijst om directe updates weer te geven en selecteer Opslaan om uw wijzigingen toe te passen.

Stap 8: Geavanceerde configuratieopties toevoegen

Breid uw widget uit met meer ingebouwde configuratiefuncties, zoals aangepaste namen en grootten.

Configuratie van naam en grootte inschakelen

Azure DevOps biedt twee configureerbare functies die standaard beschikbaar zijn:

Eigenschap Eigenschap Manifest Doel
Aangepaste namen isNameConfigurable: true Gebruikers kunnen de standaardwidgetnaam overschrijven
Meerdere grootten Meerdere supportedSizes vermeldingen Gebruikers kunnen het formaat van widgets wijzigen

Voorbeeld van uitgebreid 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"]
             }
         }
    ]
}

Geconfigureerde namen weergeven

Als u aangepaste widgetnamen wilt weergeven, werkt u de widget bij voor gebruik 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);
    }
}

Nadat u de extensie hebt bijgewerkt, kunt u zowel de widgetnaam als de grootte configureren:

Schermopname die laat zien waar de widgetnaam en -grootte kunnen worden geconfigureerd.

Uw extensie opnieuw verpakken en bijwerken om deze geavanceerde configuratieopties in te schakelen.

Gefeliciteerd! U hebt een volledige, configureerbare Azure DevOps-dashboardwidget gemaakt met mogelijkheden voor livevoorbeelden en opties voor het aanpassen van gebruikers.