Добавление мини-приложения панели мониторинга

Сервисы Azure DevOps | Azure DevOps Server | Azure DevOps Server 2022

Мини-приложения реализуются как вклад в платформу расширений. Одно расширение может включать несколько компонентов виджетов. В этой статье показано, как создать расширение, которое предоставляет одно или несколько мини-приложений.

Совет

Для получения последних рекомендаций по разработке расширений, включая темизацию и миграцию из VSS.SDK, смотрите на портале разработчика пакета SDK для расширений Azure DevOps.

Совет

Если вы создаёте новое расширение Azure DevOps, попробуйте сначала использовать эти поддерживаемые образцы коллекций — они работают с текущими сборками продуктов и охватывают современные сценарии (например, добавление вкладок на страницы pull request).

Если пример не работает в вашей организации, установите его в личную или тестовую организацию и сравните целевые идентификаторы манифеста расширения и версии API с текущими документами. Справочные материалы и API см. в статье:

Предварительные условия

Требование Описание
Знания по программированию Знания JavaScript, HTML и CSS для разработки мини-приложений
Организация Azure DevOps Создание организации
Текстовый редактор Мы используем Visual Studio Code для учебников
Node.js Последняя версия Node.js
Кроссплатформенный интерфейс командной строки tfx-cli для расширений пакетов
Установка с помощью: npm i -g tfx-cli
Каталог проекта Домашний каталог с этой структурой после выполнения руководства:

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

Обзор руководств

В этом руководстве описывается разработка мини-приложений с помощью трех прогрессивных примеров:

Часть Фокус Что вы узнаете
Часть 1. Hello World Базовое создание мини-приложения Создание мини-приложения, отображающего текст
Часть 2. Интеграция REST API Вызовы API Azure DevOps Добавление функций REST API для получения и отображения данных
Часть 3. Конфигурация мини-приложения Настройка пользователя Реализуйте параметры конфигурации для вашего мини-приложения

Совет

Если вы предпочитаете напрямую переходить к рабочим примерам, включенные примеры (см. предыдущую заметку) показывают набор мини-приложений, которые можно упаковать и опубликовать.

Прежде чем начать, ознакомьтесь с основными стилями мини-приложений и структурными рекомендациями, которые мы предоставляем.

Часть 1. Hello World

Создайте базовое мини-приложение, отображающее "Hello World" с помощью JavaScript. Основа демонстрирует основные концепции разработки виджетов.

Снимок экрана: панель мониторинга

Установка клиентского пакета SDK

Пакет SDK VSS позволяет мини-приложению взаимодействовать с Azure DevOps. Установите его с помощью npm:

npm install vss-web-extension-sdk

Скопируйте файл VSS.SDK.min.js из vss-web-extension-sdk/lib в вашу папку home/sdk/scripts.

Для получения дополнительной информации о документации SDK см. страницу GitHub клиентского SDK.

Создание структуры HTML

Создайте hello-world.html в каталоге проекта. Этот файл предоставляет макет мини-приложения и ссылки на необходимые скрипты.

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

Мини-приложения выполняются в iframes, поэтому большинство элементов головной части HTML игнорируется фреймворком, за исключением <script> и <link>.

Добавление мини-приложения JavaScript

Чтобы реализовать функциональные возможности мини-приложения, добавьте этот скрипт в <head> раздел HTML-файла:

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

Ключевые компоненты JavaScript

Функция Цель
VSS.init() Инициализирует взаимодействие между мини-приложением и Azure DevOps
VSS.require() Загружает необходимые библиотеки SDK и вспомогательные компоненты для виджетов.
VSS.register() Регистрирует виджет с уникальным идентификатором
WidgetHelpers.IncludeWidgetStyles() Применяет стили Azure DevOps по умолчанию
VSS.notifyLoadSucceeded() Уведомляет платформу о успешной загрузке

Это важно

Имя мини-приложения в VSS.register() должно соответствовать id в манифесте расширения (шаг 5).

Добавление образов расширений

Создайте необходимые образы для расширения:

  • Логотип расширения: изображение пикселя 98x98 с именем logo.png в папке img
  • Значок каталога мини-приложений: изображение пикселя 98x98 с именем CatalogIcon.png в папке img
  • Предварительный просмотр виджета: изображение размером 330x160 пикселей с именем preview.png в папке img

Эти изображения отображаются в каталоге приложений Marketplace и мини-приложений при просмотре доступных расширений.

Создание манифеста расширения

Создайте vss-extension.json в корневом каталоге проекта. Этот файл определяет метаданные и вклад расширения:

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

Это важно

Замените "publisher": "fabrikam" фактическим именем издателя. Узнайте, как создать издателя.

Основные свойства манифеста

Секция Цель
Основные сведения Имя расширения, версия, описание и издатель
значки Пути к визуальным ресурсам расширения
Взносы Определения мини-приложений, включая идентификатор, тип и свойства
Файлы Все файлы для включения в пакет расширения

Для получения полной документации по манифесту см. справочник по манифесту расширения.

Упаковка и публикация расширения

Упаковайте расширение и опубликуйте его в Visual Studio Marketplace.

Установка средства упаковки

npm i -g tfx-cli

Создание пакета расширения

В каталоге проекта выполните следующую команду:

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

Это действие создает .vsix файл, содержащий упаковаемое расширение.

Настройка издателя

  1. Перейдите на портал публикации Visual Studio Marketplace.
  2. Войдите и создайте издателя, если у вас его нет.
  3. Выберите уникальный идентификатор издателя (используемый в файле манифеста).
  4. Обновите vss-extension.json, чтобы использовать ваше имя издателя вместо "fabrikam."

Загрузите ваше расширение

  1. На портале публикации выберите "Отправить новое расширение".
  2. Выберите .vsix файл и загрузите его.
  3. Поделитесь расширением с организацией Azure DevOps.

Кроме того, используйте командную строку:

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

Совет

Используется --rev-version для автоматического увеличения номера версии при обновлении существующего расширения.

Установка и проверка мини-приложения

Чтобы проверить, добавьте мини-приложение на панель мониторинга:

  1. Перейдите в проект Azure DevOps: https://dev.azure.com/{Your_Organization}/{Your_Project}
  2. Перейдите на Обзор>панели мониторинга.
  3. Выберите Добавить виджет.
  4. Найдите мини-приложение в каталоге и нажмите кнопку "Добавить".

Мини-приложение Hello World отображается на панели мониторинга, отображая настроенный текст.

Следующий шаг. Перейдите к части 2 , чтобы узнать, как интегрировать REST API Azure DevOps в мини-приложение.

Часть 2. Hello World с REST API Azure DevOps

Расширьте мини-приложение для взаимодействия с данными Azure DevOps с помощью REST API. В этом примере показано, как получить сведения о запросах и отображать его динамически в мини-приложении.

В этой части используйте REST API отслеживания рабочих элементов для получения сведений о существующем запросе и отображения сведений о запросе под текстом Hello World.

Снимок экрана: панель мониторинга

Создание HTML-файла

Создайте новый файл мини-приложения, который основан на предыдущем примере. Скопируйте hello-world.html и переименуйте в hello-world2.html. Теперь структура проекта включает:

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

Обновление структуры HTML мини-приложения

Внесите изменения в hello-world2.html.

  1. Добавьте контейнер для данных запроса: включите новый <div> элемент для отображения сведений о запросе.
  2. Обновите идентификатор мини-приложения: измените имя мини-приложения с HelloWorldWidget на HelloWorldWidget2 для уникальной идентификации.
<!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>

Конфигурация разрешений API

Перед вызовами REST API настройте необходимые разрешения в манифесте расширения.

Добавление рабочей области

Область vso.work предоставляет доступ только для чтения к рабочим элементам и запросам. Добавьте эту область в ваш vss-extension.json.

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

Пример манифеста

Для полного манифеста с другими свойствами структура выглядит следующим образом:

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

Это важно

Ограничения области. Добавление или изменение областей после публикации не поддерживается. Если вы уже опубликовали расширение, сначала необходимо удалить его из Marketplace. Перейдите на портал публикации Visual Studio Marketplace, найдите расширение и нажмите кнопку "Удалить".

Реализация вызовов REST API

Azure DevOps предоставляет клиентские библиотеки REST JavaScript через пакет SDK. Эти библиотеки упаковывают вызовы AJAX и сопоставляют ответы API на доступные объекты.

Обновление мини-приложения JavaScript

Замените вызов VSS.require в вашем hello-world2.html, чтобы включить REST клиент для отслеживания рабочих элементов.

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

Основные детали реализации

Компонент Цель
WorkItemTrackingRestClient.getClient() Получает экземпляр клиента REST для отслеживания рабочих элементов
getQuery() Извлекает сведения о запросах, упакованные в обещание
WidgetStatusHelper.Failure() Обеспечивает согласованную обработку ошибок для сбоев виджетов
projectId Текущий контекст проекта, необходимый для вызовов API

Совет

Пользовательские пути запроса: если у вас нет запроса "Отзыв" в разделе "Общие запросы", замените "Shared Queries/Feedback" путь к любому запросу, существующему в проекте.

Шаг 4. Отображение данных ответа API

Отобразите сведения запроса в вашем мини-приложении, обработав ответ REST API.

Добавление визуализации данных запроса

Замените // Process query data комментарий этой реализацией:

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

Метод getQuery() возвращает Contracts.QueryHierarchyItem объект со свойствами метаданных запроса. В этом примере отображаются три ключевых фрагмента информации под текстом Hello World.

Полный пример

Окончательный hello-world2.html файл должен выглядеть следующим образом:

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

        VSS.require(["AzureDevOps/Dashboards/WidgetHelpers", "AzureDevOps/WorkItemTracking/RestClient"], 
            function (WidgetHelpers, 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>

Обновление манифеста

Чтобы сделать его доступным в каталоге мини-приложений, добавьте новое мини-приложение в манифест расширения.

Добавление вклада мини-приложения

Обновите vss-extension.json, чтобы включить мини-приложение с поддержкой REST API. Добавьте этот вклад в 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"
    ]
}

Совет

Предварительныйpreview2.png просмотр изображения: создайте изображение (330x160 пикселей) и поместите его в папкуimg, чтобы показать пользователям, как выглядит мини-приложение в каталоге.

Упаковка и публикация

Упакуйте, опубликуйте и делитесь своим расширением. Если вы уже опубликовали расширение, вы можете перепаковать и обновить его непосредственно в Marketplace.

Тестирование мини-приложения REST API

Чтобы просмотреть интеграцию REST API в действии, добавьте новое мини-приложение на панель мониторинга:

  1. Перейдите в проект Azure DevOps: https://dev.azure.com/{Your_Organization}/{Your_Project}
  2. Выберите Обзор>Панели.
  3. Выберите Добавить виджет.
  4. Найдите "Hello World Widget 2 (с API)" и нажмите кнопку "Добавить".

В расширенном мини-приложении отображаются текст "Hello World" и сведения о динамических запросах из проекта Azure DevOps.

Дальнейшие действия. Перейдите к части 3 , чтобы добавить параметры конфигурации, позволяющие пользователям настраивать отображаемый запрос.

Часть 3. Настраиваемое мини-приложение

Дополните часть 2, добавив возможности конфигурации пользователей в ваше мини-приложение. Вместо жесткого написания кода пути запроса создайте интерфейс конфигурации, позволяющий пользователям выбирать запрос для отображения с функциями динамической предварительной версии.

В этой части показано, как создавать настраиваемые мини-приложения, которые пользователи могут настраивать в соответствии со своими потребностями, предоставляя отзывы в режиме реального времени во время настройки.

Снимок экрана: живое предосмотр виджета на панели мониторинга Обзор с учётом изменений.

Создание файлов конфигурации

Конфигурации мини-приложений используют множество сходств с мини-приложениями сами по себе— используют один и тот же пакет SDK, структуру HTML и шаблоны JavaScript, но служат различным целям в рамках платформы расширений.

структура проекта

Чтобы поддерживать конфигурацию мини-приложения, создайте два новых файла:

  1. Скопируйте hello-world2.html и переименуйте его в hello-world3.html, настраиваемый виджет.
  2. Создайте новый файл с именем configuration.html, который обрабатывает интерфейс конфигурации.

Теперь структура проекта включает:

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

Интерфейс конфигурации

Добавьте эту структуру HTML в configuration.html, которое создаёт раскрывающийся селектор для выбора запросов.

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

Реализация логики конфигурации

Конфигурация JavaScript соответствует тому же шаблону инициализации, что и мини-приложения, но реализует IWidgetConfiguration контракт вместо базового IWidget контракта.

Код конфигурации

Вставьте этот скрипт в <head> раздел 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>

Договор конфигурации

Контракт IWidgetConfiguration требует следующие основные функции:

Функция Цель При вызове
load() Инициализация пользовательского интерфейса конфигурации с существующими параметрами При открытии диалогового окна конфигурации
onSave() Сериализация входных данных пользователей и проверка параметров Когда пользователь выбирает "Сохранить"

Совет

Сериализация данных: в этом примере используется JSON для сериализации параметров. Мини-приложение обращается к этим параметрам widgetSettings.customSettings.data и должно десериализировать их соответствующим образом.

Включение динамической предварительной версии

Динамическая предварительная версия позволяет пользователям сразу просматривать изменения мини-приложений при изменении параметров конфигурации, предоставляя мгновенные отзывы перед сохранением.

Уведомления об изменениях

Чтобы включить динамическую предварительную версию, добавьте этот обработчик событий в функцию load :

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

Полный файл конфигурации

Ваш результат configuration.html должен выглядеть следующим образом:

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

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

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

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

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

Это важно

Активировать кнопку «Сохранить»: фреймворк требует по крайней мере одного уведомления об изменении конфигурации, чтобы активировать кнопку Сохранить. Обработчик событий изменений гарантирует, что это действие происходит при выборе пользователем параметра.

Сделать мини-приложение настраиваемым

Преобразуйте виджет из части 2, чтобы использовать конфигурационные данные вместо жестко закодированных значений. Для этого шага требуется реализация IConfigurableWidget контракта.

Обновление регистрации мини-приложения

Внесите hello-world3.html следующие изменения:

  1. Обновление идентификатора мини-приложения: переход с HelloWorldWidget2 на HelloWorldWidget3.
  2. Добавьте функцию перезагрузкиIConfigurableWidget: реализуйте контракт.
return {
    load: function (widgetSettings) {
        // Set your title
        var $title = $('h2.title');
        $title.text('Hello World');

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

Обработка данных конфигурации

Обновите функцию getQueryInfo , чтобы использовать параметры конфигурации вместо жестко закодированных путей запроса:

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

Жизненный цикл мини-приложения

Функция Цель Рекомендации по использованию
load() Начальная отрисовка виджета и однократная настройка Тяжелые операции, инициализация ресурсов
reload() Обновление мини-приложения с новой конфигурацией Упрощенное обновление, обновление данных

Совет

Оптимизация производительности. Используйте load() для дорогостоящих операций, которые должны выполняться только один раз, и reload() для быстрого обновления при изменении конфигурации.

Добавить лайтбокс (необязательно)

Мини-приложения панели мониторинга имеют ограниченное пространство, что затрудняет отображение исчерпывающей информации. Lightbox предоставляет элегантное решение, показывая подробные данные в модальном наложении, не переходя от Dashboard.

Преимущества Описание
Эффективность использования пространства Держите мини-приложение компактным, предоставляя подробные представления
Возможности для пользователя Сохранение контекста панели мониторинга при отображении дополнительных сведений
Прогрессивное раскрытие информации Отображение сводных данных в мини-приложении, сведения по запросу
адаптивный дизайн Адаптация к различным размерам экрана и конфигурациям мини-приложений

Элементы, доступные для щелчка

Обновите отрисовку данных запроса, чтобы включить кликабельные элементы, которые активируют лайтбокс:

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

Добавьте эту реализацию lightbox в ваш виджет 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');
        }
    });
}

Включите CSS-стили для лайтбокса в разделе HTML вашего виджета <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>

Полная реализация виджета

Полностью расширенное мини-приложение с функцией lightbox.

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

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

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

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

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

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

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

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

Рекомендации по специальным возможностям. Убедитесь, что светлая коробка доступна для клавиатуры и содержит правильные метки для средств чтения с экрана. Тестируйте встроенные функции специальных возможностей Azure DevOps.

Это важно

Производительность: лайтбоксы должны быстро загружаться. Рассмотрите возможность отложенной загрузки подробных данных только при открытии лайтбокса, вместо загрузки всех данных заранее.

Настройка манифеста

Регистрация как настраиваемого мини-приложения

Вклад виджетов и настройки конфигурации

Обновление vss-extension.json для включения двух новых вкладов:

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

Требования к вкладу

Свойство Цель Обязательное значение
type Определяет конфигурацию виджета в качестве вклада ms.vss-dashboards-web.widget-configuration
targets Расположение конфигурации ms.vss-dashboards-web.widget-configuration
uri Путь к HTML-файлу конфигурации Путь к файлу конфигурации

Шаблон таргетинга виджетов

Для настраиваемых targets мини-приложений массив должен содержать ссылку на конфигурацию:

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

Предупреждение

Видимость кнопки конфигурации. Если мини-приложение не правильно нацеливает свой вклад в конфигурацию, кнопка Настроить не отображается. Убедитесь, что имена издателя и расширения точно соответствуют вашему манифесту.

Упаковка и публикация

Разверните расширенное расширение с возможностями конфигурации.

Если это первая публикация, выполните шаг 6. Упаковка, публикация и общий доступ. Для существующих расширений перепакуйте и обновите их непосредственно в Marketplace.

Тестирование настраиваемого мини-приложения

Выполните полный рабочий процесс конфигурации, добавив и настроив мини-приложение.

Добавление на панель мониторинга

  1. Перейти к https://dev.azure.com/{Your_Organization}/{Your_Project}.
  2. Перейдите на Обзор>панели мониторинга.
  3. Выберите Добавить виджет.
  4. Найдите "Hello World Widget 3 (с конфигурацией)" и нажмите кнопку "Добавить".

Запрос конфигурации отображается, так как мини-приложение требует установки:

Снимок экрана: панель мониторинга

Настройка мини-приложения

Доступ к конфигурации с помощью любого метода:

  • Меню мини-приложения: наведите указатель мыши на мини-приложение, выберите многоточие (⋯), а затем настройте
  • Режим редактирования панели мониторинга: выберите "Изменить " на панели мониторинга, а затем кнопку "Настройка" в мини-приложении

Откроется панель конфигурации с динамической предварительной версией в центре. Выберите запрос из раскрывающегося списка, чтобы увидеть немедленные обновления, а затем нажмите кнопку "Сохранить ", чтобы применить изменения.

Добавление дополнительных параметров

Расширьте мини-приложение с дополнительными встроенными функциями конфигурации, такими как пользовательские имена и размеры.

Параметры имени и размера

Azure DevOps предоставляет две настраиваемые функции:

Функция Свойство манифеста Цель
Пользовательские имена isNameConfigurable: true Пользователи могут переопределить имя мини-приложения по умолчанию
Несколько размеров Несколько supportedSizes записей Пользователи могут изменять размер мини-приложений

Пример манифеста

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

Отображение настроенных имен

Чтобы отобразить имена пользовательских виджетов, обновите ваш виджет для использования 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);
    }
}

После обновления расширения можно настроить как имя мини-приложения, так и размер:

Снимок экрана, показывающий, где можно настроить имя и размер мини-приложения.

Перепакуйте и обновите расширение, чтобы включить эти расширенные параметры конфигурации.

Поздравляю! Вы создали полное настраиваемое мини-приложение панели мониторинга Azure DevOps с возможностями динамической предварительной версии и параметрами настройки пользователей.