Поделиться через


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

Azure DevOps Services | Azure DevOps Server 2022 — Azure DevOps Server 2019

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

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

Совет

Ознакомьтесь с нашей новой документацией по разработке расширений с помощью пакета SDK для расширений Azure DevOps.

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

Требование Описание
Знания по программированию Знания 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. Конфигурация мини-приложения Настройка пользователя Реализуйте параметры конфигурации для вашего мини-приложения

Совет

Пропустить туториал: Скачайте полный образец расширения, перейдите в widgets папку и переключитесь на шаг 6, чтобы опубликовать три готовых примера виджетов.

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

Часть 1. Hello World

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

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

Шаг 1. Установка клиентского пакета 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.

Шаг 2. Создание структуры 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>.

Шаг 3. Добавление мини-приложения 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).

Шаг 4. Добавление образов расширений

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

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

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

Шаг 5. Создание манифеста расширения

Создайте 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" фактическим именем издателя. Узнайте, как создать издателя.

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

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

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

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

Упаковайте расширение и опубликуйте его в 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 для автоматического увеличения номера версии при обновлении существующего расширения.

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

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

  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.

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

Шаг 1. Создание расширенного 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>

Шаг 2. Настройка разрешений доступа к 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, найдите расширение и нажмите кнопку "Удалить".

Шаг 3. Реализация интеграции 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>

Шаг 5. Обновление манифеста расширения

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

Добавьте второй элемент виджета

Обновите 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, чтобы показать пользователям, как выглядит мини-приложение в каталоге.

Шаг 6. Упаковка, публикация и общий доступ

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

Шаг 7. Тестирование мини-приложения 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. Настройка Hello World

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

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

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

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

Конфигурации мини-приложений используют множество сходств с мини-приложениями сами по себе— используют один и тот же пакет 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>

Шаг 2. Реализация JavaScript конфигурации

Конфигурация 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 и должно десериализировать их соответствующим образом.

Шаг 3. Включение функций динамической предварительной версии

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

Реализация уведомлений об изменениях

Чтобы включить динамическую предварительную версию, добавьте этот обработчик событий в функцию 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>

Это важно

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

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

Преобразуйте виджет из части 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.

Зачем использовать lightbox в мини-приложениях?

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

Реализация элементов, доступных для щелчка

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

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

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

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

Создание функции lightbox

Добавьте эту реализацию 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.

Это важно

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

Шаг 5. Настройка манифеста расширения

Зарегистрируйте настраиваемое мини-приложение и его интерфейс конфигурации в манифесте расширения.

Добавление виджетов и элементов конфигурации

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

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

Требования к участию в конфигурации

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

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

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

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

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

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

Шаг 6. Упаковка, публикация и общий доступ

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

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

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

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

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

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

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

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

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

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

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

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

Шаг 8. Добавление дополнительных параметров конфигурации

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

Включение настройки имени и размера

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 с возможностями динамической предварительной версии и параметрами настройки пользователей.