Dela via


Lägga till en instrumentpanelswidget

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

Widgetar implementeras som bidrag i tilläggsramverket. Ett enda tillägg kan innehålla flera widgetbidrag. Den här artikeln visar hur du skapar ett tillägg som innehåller en eller flera widgetar.

Dricks

Kolla in vår senaste dokumentation om utveckling av tillägg med Azure DevOps Extension SDK.

Dricks

Om du startar ett nytt Azure DevOps-tillägg kan du prova dessa underhållsexempelsamlingar först – de fungerar med aktuella produktversioner och täcker moderna scenarier (till exempel att lägga till flikar på sidor med pull-begäranden).

Installera det i en personlig organisation eller testorganisation om ett exempel inte fungerar i din organisation och jämför mål-ID för tilläggsmanifestet och API-versioner med de aktuella dokumenten. För referens och API:er, se:

Förutsättningar

Krav Beskrivning
Programmeringskunskaper Kunskaper om JavaScript, HTML och CSS för widgetutveckling
Azure DevOps-organisation Skapa en organisation
Textredigerare Vi använder Visual Studio Code för självstudier
Node.js Senaste versionen av Node.js
Plattformsoberoende CLI tfx-cli för att pakettera tillägg
Installera med: npm i -g tfx-cli
Projektkatalog Hemkatalog med den här strukturen när du har slutfört självstudien:

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

Översikt över självstudier

I den här självstudien lär du dig utveckling av widgetar genom tre progressiva exempel:

Del Fokus Det här lär du dig
Del 1: Hello World Grundläggande skapande av widget Skapa en widget som visar text
Del 2: REST API-integrering Azure DevOps API-anrop Lägga till REST API-funktioner för att hämta och visa data
Del 3: Widgetkonfiguration Användaranpassning Implementera konfigurationsalternativ för widgeten

Dricks

Om du föredrar att gå direkt till fungerande exempel visar de inkluderade exemplen (se föregående anteckning) en uppsättning widgetar som du kan paketera och publicera.

Innan du börjar kan du läsa de grundläggande widgetformaten och den strukturella vägledning som vi tillhandahåller.

Del 1: Hello World

Skapa en grundläggande widget som visar "Hello World" med JavaScript. Den här grunden visar de grundläggande begreppen för widgetutveckling.

Skärmbild av översiktsinstrumentpanelen med en exempelwidget.

Steg 1: Installera klient-SDK

VSS SDK gör att widgeten kan kommunicera med Azure DevOps. Installera den med npm:

npm install vss-web-extension-sdk

VSS.SDK.min.js Kopiera filen från vss-web-extension-sdk/lib till mappenhome/sdk/scripts.

Mer SDK-dokumentation finns på GitHub-sidan för Klient-SDK.

Steg 2: Skapa HTML-strukturen

Skapa hello-world.html i projektkatalogen. Den här filen innehåller widgetens layout och referenser till nödvändiga skript.

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

Widgetar körs i iframes, så de flesta HTML-huvudelement utom <script> och <link> ignoreras av ramverket.

Steg 3: Lägg till JavaScript-widget

Om du vill implementera widgetfunktionen lägger du till det här skriptet i <head> avsnittet i HTML-filen:

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

Viktiga JavaScript-komponenter

Funktion Avsikt
VSS.init() Initierar kommunikationen mellan widgeten och Azure DevOps
VSS.require() Läser in nödvändiga SDK-bibliotek och widgethjälpare
VSS.register() Registrerar widgeten med en unik identifierare
WidgetHelpers.IncludeWidgetStyles() Tillämpar standardformatering för Azure DevOps
VSS.notifyLoadSucceeded() Meddelar ramverket att inläsningen har slutförts

Viktigt!

Widgetnamnet i VSS.register() måste matcha id i tilläggsmanifestet (steg 5).

Steg 4: Lägg till tilläggsbilder

Skapa de avbildningar som krävs för tillägget:

  • Tilläggslogotyp: bild med 98 x 98 bildpunkter med namnet logo.png i img mappen
  • Widgetkatalogikon: bild med 98 x 98 bildpunkter med namnet CatalogIcon.png i img mappen
  • Förhandsgranskning av widget: bild med 330 x 160 bildpunkter med namnet preview.png i img mappen

Dessa bilder visas i Marketplace- och widgetkatalogen när användarna bläddrar bland tillgängliga tillägg.

Steg 5: Skapa tilläggsmanifestet

Skapa vss-extension.json i projektets rotkatalog. Den här filen definierar ditt tilläggs metadata och bidrag:

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

Viktigt!

Ersätt "publisher": "fabrikam" med ditt faktiska utgivarnamn. Lär dig hur du skapar en utgivare.

Viktiga manifestegenskaper

Sektion Avsikt
Grundläggande information Tilläggsnamn, version, beskrivning och utgivare
ikoner Sökvägar till tilläggets visuella tillgångar
Bidrag Widgetdefinitioner inklusive ID, typ och egenskaper
Filer Alla filer som ska inkluderas i tilläggspaketet

Fullständig manifestdokumentation finns i Tilläggsmanifestreferens.

Steg 6: Paketera och publicera tillägget

Paketera tillägget och publicera det på Visual Studio Marketplace.

Installera förpackningsverktyget

npm i -g tfx-cli

Skapa ditt tilläggspaket

Kör kommandot från din projektkatalog:

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

Den här åtgärden skapar en .vsix fil som innehåller ditt paketerade tillägg.

Konfigurera en utgivare

  1. Gå till Visual Studio Marketplace-publiceringsportalen.
  2. Logga in och skapa en utgivare om du inte har någon.
  3. Välj en unik utgivaridentifierare (används i manifestfilen).
  4. Uppdatera ditt vss-extension.json för att använda ditt utgivarnamn i stället för "fabrikam".

Ladda upp tillägget

  1. I publiceringsportalen väljer du Ladda upp nytt tillägg.
  2. Välj din .vsix fil och ladda upp den.
  3. Dela tillägget med din Azure DevOps-organisation.

Du kan också använda kommandoraden:

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

Dricks

Använd --rev-version för att automatiskt öka versionsnumret när du uppdaterar ett befintligt tillägg.

Steg 7: Installera och testa widgeten

Om du vill testa lägger du till widgeten på en instrumentpanel:

  1. Gå till ditt Azure DevOps-projekt: https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Gå till Översikt>Instrumentpaneler.
  3. Välj Lägg till en widget.
  4. Leta upp widgeten i katalogen och välj Lägg till.

Widgeten "Hello World" visas på instrumentpanelen och visar den text som du har konfigurerat.

Nästa steg: Fortsätt till del 2 för att lära dig hur du integrerar Rest-API:er för Azure DevOps i din widget.

Del 2: Hello World med Azure DevOps REST API

Utöka widgeten så att den interagerar med Azure DevOps-data med hjälp av REST-API:er. Det här exemplet visar hur du hämtar frågeinformation och visar den dynamiskt i widgeten.

I den här delen använder du REST-API:et för arbetsobjektspårning för att hämta information om en befintlig fråga och visa frågeinformationen under texten "Hello World".

Skärmbild av översiktsinstrumentpanelen med en exempelwidget med hjälp av REST API för WorkItemTracking.

Steg 1: Skapa den förbättrade HTML-filen

Skapa en ny widgetfil som bygger på föregående exempel. Kopiera hello-world.html och byt namn på den till hello-world2.html. Projektstrukturen innehåller 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

Uppdatera HTML-widgetens struktur

Gör dessa ändringar i hello-world2.html:

  1. Lägg till en container för frågedata: Inkludera ett nytt <div> element för att visa frågeinformation.
  2. Uppdatera widgetidentifieraren: Ändra widgetnamnet från HelloWorldWidget till HelloWorldWidget2 för unik identifiering.
<!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>

Steg 2: Konfigurera API-åtkomstbehörigheter

Innan du gör REST API-anrop konfigurerar du de behörigheter som krävs i tilläggsmanifestet.

Lägg till arbetsomfånget

Omfånget vso.work ger skrivskyddad åtkomst till arbetsobjekt och frågor. Lägg till det här omfånget i vss-extension.json:

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

Exempel på fullständigt manifest

För ett fullständigt manifest med andra egenskaper kan du strukturera det så här:

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

Viktigt!

Omfångsbegränsningar: Det går inte att lägga till eller ändra omfång efter publicering. Om du redan har publicerat tillägget måste du ta bort det från Marketplace först. Gå till Visual Studio Marketplace-publiceringsportalen, leta reda på tillägget och välj Ta bort.

Steg 3 i API-processen: Implementera REST API-integration

Azure DevOps tillhandahåller JavaScript REST-klientbibliotek via SDK:t. De här biblioteken omsluter AJAX-anrop och mappar API-svar till användbara objekt.

Uppdatera javascript-widgeten

Ersätt anropet VSS.require i din hello-world2.html för att inkludera REST-klienten för arbetsobjektspårning:

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

Viktig implementeringsinformation

Komponent Avsikt
WorkItemTrackingRestClient.getClient() Hämtar en instans av REST-klienten för arbetsobjektspårning
getQuery() Hämtar frågeinformation omsluten i ett löfte
WidgetStatusHelper.Failure() Ger konsekvent felhantering vid widgetfel
projectId Aktuell projektkontext som krävs för API-anrop

Dricks

Anpassade frågesökvägar: Om du inte har en "Feedback"-fråga i "Delade frågor" ersätter "Shared Queries/Feedback" du med sökvägen till alla frågor som finns i projektet.

Steg 4: Visa API-svarsdata

Rendera frågeinformationen i widgeten genom att bearbeta REST API-svaret.

Lägga till frågedataåtergivning

Ersätt kommentaren // Process query data med den här implementeringen:

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

Metoden getQuery() returnerar ett Contracts.QueryHierarchyItem objekt med egenskaper för frågemetadata. I det här exemplet visas tre viktiga informationsdelar under texten "Hello World".

Fullständigt arbetsexempel

Den slutliga hello-world2.html filen bör se ut så här:

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

Steg 5: Uppdatera tilläggsmanifestet

Om du vill göra den tillgänglig i widgetkatalogen lägger du till din nya widget i tilläggsmanifestet.

Lägg till det andra widgetbidraget

Uppdatera vss-extension.json för att inkludera din REST API-aktiverade widget. Lägg till det här bidraget i matrisen contributions :

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

Dricks

Förhandsgranskningsbild: Skapa en preview2.png bild (330 x 160 bildpunkter) och placera den img i mappen för att visa användarna hur widgeten ser ut i katalogen.

Steg 6: Paketera, publicera och dela

Paketera, publicera och dela tillägget. Om du redan har publicerat tillägget kan du paketera om och uppdatera det direkt på Marketplace.

Steg 7: Testa rest-API-widgeten

Om du vill visa REST API-integreringen i praktiken lägger du till den nya widgeten på instrumentpanelen:

  1. Gå till ditt Azure DevOps-projekt: https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Välj Översikt>Instrumentpaneler.
  3. Välj Lägg till en widget.
  4. Leta upp "Hello World Widget 2 (med API)" och välj Lägg till.

Den förbättrade widgeten visar både texttexten "Hello World" och livefrågeinformationen från ditt Azure DevOps-projekt.

Nästa steg: Fortsätt till del 3 för att lägga till konfigurationsalternativ som låter användarna anpassa vilken fråga som ska visas.

Del 3: Konfigurera Hello World

Bygg vidare på del 2 genom att lägga till användarkonfigurationsmöjligheter i widgeten. I stället för att hårdkoda frågesökvägen skapar du ett konfigurationsgränssnitt som låter användarna välja vilken fråga som ska visas, med live-förhandsgranskningsfunktioner.

Den här delen visar hur du skapar konfigurerbara widgetar som användarna kan anpassa efter sina specifika behov samtidigt som de ger feedback i realtid under konfigurationen.

Skärmbild av översiktsinstrumentpanelens liveförhandsgranskning av widgeten baserat på ändringar.

Steg 1: Skapa konfigurationsfiler

Widgetkonfigurationer delar många likheter med själva widgetarna – båda använder samma SDK, HTML-struktur och JavaScript-mönster, men har olika syften inom tilläggsramverket.

Konfigurera projektstrukturen

Skapa två nya filer för att stödja widgetkonfiguration:

  1. Kopiera hello-world2.html och byt namn på den till hello-world3.html, din konfigurerbara widget.
  2. Skapa en ny fil med namnet configuration.html, som hanterar konfigurationsgränssnittet.

Projektstrukturen innehåller 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

Skapa konfigurationsgränssnittet

Lägg till den här HTML-strukturen i configuration.html, som skapar en listruteväljare för att välja frågor:

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

Steg 2: Implementera konfiguration av JavaScript

JavaScript-konfigurationen följer samma initieringsmönster som widgetar, men implementerar IWidgetConfiguration kontraktet i stället för det grundläggande IWidget kontraktet.

Lägga till konfigurationslogik

Infoga det här skriptet <head> i avsnittet 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>

Information om konfigurationskontrakt

Kontraktet IWidgetConfiguration kräver följande nyckelfunktioner:

Funktion Avsikt När det anropas
load() Initiera konfigurationsgränssnittet med befintliga inställningar När konfigurationsdialogrutan öppnas
onSave() Serialisera användarindata och verifiera inställningar När användaren väljer Spara

Dricks

Dataserialisering: I det här exemplet används JSON för att serialisera inställningar. Widgeten kommer åt dessa inställningar via widgetSettings.customSettings.data och måste deserialisera dem i enlighet med detta.

Steg 3: Aktivera funktioner för liveförhandsgranskning

Med liveförhandsgranskning kan användarna se widgetändringar omedelbart när de ändrar konfigurationsinställningarna, vilket ger omedelbar feedback innan de sparar.

Implementera ändringsmeddelanden

Om du vill aktivera liveförhandsgranskning lägger du till den här händelsehanteraren i load funktionen:

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

Fullständig konfigurationsfil

Din slutliga configuration.html bör se ut så här:

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

Viktigt!

Aktivera knappen Spara: Ramverket kräver minst ett meddelande om konfigurationsändring för att aktivera knappen Spara . Händelsehanteraren för ändring ser till att den här åtgärden inträffar när användare väljer ett alternativ.

Steg 4: Gör widgeten konfigurerbar

Transformera widgeten från del 2 för att använda konfigurationsdata i stället för hårdkodade värden. Det här steget kräver att implementera kontraktet IConfigurableWidget.

Uppdatera widgetregistrering

I hello-world3.htmlgör du följande ändringar:

  1. Uppdatera widget-ID: Ändra från HelloWorldWidget2 till HelloWorldWidget3.
  2. Lägg till omladdningsfunktion: Implementera IConfigurableWidget-kontraktet.
return {
    load: function (widgetSettings) {
        // Set your title
        var $title = $('h2.title');
        $title.text('Hello World');

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

Hantera konfigurationsdata

getQueryInfo Uppdatera funktionen så att den använder konfigurationsinställningar i stället för hårdkodade frågesökvägar:

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

Skillnader i widgetens livscykel

Funktion Avsikt Användningsriktlinjer
load() Inledande widgetåtergivning och engångskonfiguration Tunga åtgärder, resursinitiering
reload() Uppdatera widget med ny konfiguration Lätta uppdateringar, datauppdatering

Dricks

Prestandaoptimering: Använd load() för dyra åtgärder som bara behöver köras en gång och reload() för snabba uppdateringar när konfigurationen ändras.

(Valfritt) Lägg till en ljusruta för detaljerad information

Instrumentpanelswidgetar har begränsat utrymme, vilket gör det svårt att visa omfattande information. En lightbox ger en elegant lösning genom att visa detaljerade data i ett modalt överlägg utan att navigera bort från instrumentpanelen.

Varför ska du använda en lightbox i widgetar?

Förmån Beskrivning
Utrymmeseffektivitet Håll widget kompakt samtidigt som du erbjuder detaljerade vyer
Användarupplevelse Underhålla instrumentpanelskontexten samtidigt som mer information visas
Progressivt avslöjande Visa sammanfattningsdata i widgeten, detaljer vid behov
dynamisk design Anpassa till olika skärmstorlekar och widgetkonfigurationer

Implementera klickbara element

Uppdatera frågedataåtergivningen så att den innehåller klickbara element som utlöser ljusrutan:

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

Skapa lightbox-funktionen

Lägg till den här lightbox-implementeringen i javascript-widgeten:

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

Lägg till lightbox-stil

Inkludera CSS-stilar för lightboxen i HTML-avsnittet <head> :

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

Förbättrad widgetimplementering

Din kompletta förbättrade widget med lightbox-funktioner:

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

Hjälpmedelsöverväganden: Se till att din ljusruta är tangentbordstillgänglig och innehåller rätt etiketter för skärmläsare. Testa med Azure DevOps inbyggda hjälpmedelsfunktioner.

Viktigt!

Prestanda: Lightbox-rutor bör läsas in snabbt. Överväg att endast ladda detaljerad data när lightboxen öppnas i stället för att hämta allt i förväg.

Steg 5: Konfigurera tilläggsmanifestet

Registrera både den konfigurerbara widgeten och dess konfigurationsgränssnitt i tilläggsmanifestet.

Lägg till widget- och konfigurationstillägg

Uppdatera vss-extension.json för att inkludera två nya bidrag:

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

Krav för konfigurationsbidrag

Egendom Avsikt Obligatoriskt värde
type Identifierar bidrag som widgetkonfiguration ms.vss-dashboards-web.widget-configuration
targets Där konfigurationen visas ms.vss-dashboards-web.widget-configuration
uri Sökväg till HTML-konfigurationsfil Din konfigurationsfilsökväg

Riktmönster för widget

För konfigurerbara widgetar måste matrisen targets innehålla en referens till konfigurationen:

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

Varning

Synlighet för konfigurationsknapp: Om widgeten inte är korrekt inriktad på sitt konfigurationsbidrag visas inte knappen Konfigurera . Kontrollera att utgivar- och tilläggsnamnen matchar manifestet exakt.

Steg 6: Paketera, publicera och dela

Distribuera ditt utökade tillägg med konfigurationsfunktioner.

Om det är din första publikation följer du Steg 6: Paketera, publicera och dela. För befintliga tillägg packar du om och uppdaterar direkt på Marketplace.

Steg 7: Testa den konfigurerbara widgeten

Upplev det fullständiga konfigurationsarbetsflödet genom att lägga till och konfigurera widgeten.

Lägg till widgeten på instrumentpanelen

  1. Gå till https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Gå till Översikt>Instrumentpaneler.
  3. Välj Lägg till en widget.
  4. Leta upp "Hello World Widget 3 (med konfiguration)" och välj Lägg till.

En konfigurationsprompt visas eftersom widgeten kräver installation:

Skärmbild av översiktsinstrumentpanelen med en exempelwidget från katalogen.

Konfigurera widgeten

Åtkomstkonfiguration via någon av metoderna:

  • Widget-menyn: Hovra över widgeten, välj ellipsen (⋯) och sedan Konfigurera
  • Redigeringsläge för instrumentpanelen: Välj Redigera på instrumentpanelen och sedan knappen konfigurera på widgeten

Konfigurationspanelen öppnas med en live-förhandsversion i mitten. Välj en fråga i listrutan för att se omedelbara uppdateringar och välj sedan Spara för att tillämpa ändringarna.

Steg 8: Lägg till avancerade konfigurationsalternativ

Utöka widgeten med fler inbyggda konfigurationsfunktioner som anpassade namn och storlekar.

Aktivera konfiguration av namn och storlek

Azure DevOps erbjuder två konfigurerbara funktioner direkt utanför boxen:

Egenskap Manifest egenskap Avsikt
Anpassade namn isNameConfigurable: true Användare kan åsidosätta standardwidgetnamnet
Flera storlekar Flera supportedSizes poster Användare kan ändra storlek på widgetar

Förbättrat manifestexempel

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

Visa konfigurerade namn

Om du vill visa anpassade widgetnamn uppdaterar du widgeten så att den använder 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);
    }
}

När du har uppdaterat tillägget kan du konfigurera både widgetens namn och storlek:

Skärmbild som visar var widgetens namn och storlek kan konfigureras.

Packa om och uppdatera tillägget för att aktivera de här avancerade konfigurationsalternativen.

Grattis! Du har skapat en fullständig, konfigurerbar Azure DevOps-instrumentpanelswidget med funktioner för liveförhandsgranskning och alternativ för användaranpassning.