Создание дашбордов на Elixir с помощью Kitto

Kitto — новый фреймворк для Elixir, который, можно сказать, является возрождением фреймворка Dashing, изначально разработанного компанией Shopify для Ruby. Для тех, кто не слышал ни о том, ни о другом, Kitto — фреймворк для создания дашбордов для вывода данных. Данные для виджетов дашборда передаются через джобы — Jobs, которые опрашивают веб-сервисы, базы данных и другие источники информации по заранее определённому графику.

Учитывая, что джобы — основное средство вывода данных в дашборд (по крайней мере пока), было бы неплохо рассказать о том, как написать простой джоб. При этом я хотел бы немного выйти за рамки возможностей обычных джобов из образца дашборда, который идёт в комплекте с Kitto. Отмечу, что со своими коллегами в Cage Data мы часто прибегаем к использованию системы JIRA и что джоб, о котором пойдёт речь, я написал для одного из наших дашбордов.

В этой статье вопрос о том, как создать новый дашборд в Kitto освещаться не будет, так как в руководстве Kitto подробнейшим образом расписано всё об установке и создании дашбордов. Создав дашборд, нужно добавить библиотеки HTTPoison и Poison в список зависимостей приложения.

Получение данных

Вместо того, чтобы заворачивать запрос к API в джоб-файл, попробуем выделить как можно больше логики в отдельный модуль. Это позволит использовать одну и ту же логику во всех джобах и, если модуль окажется слишком объёмным, перенести часть кода в пакет, указав его в зависимостях.

Для взаимодействия с интерфейсами JIRA нужно сначала где-то прописать сервер, имя пользователь и пароль. Где хранить всё это добро — решать только вам. В приведённом примере я добавлю их через переменные окружения приложения в виде :jira_url, :jira_username и :jira_password.

Укромно спрятав данные доступа к базе, приступим к созданию модуля в lib/apis/jira.ex, который будет обмениваться данными с JIRA API. Я планирую отобразить на дашборде два виджета со следующей информацией: 1) количество открытых багов во всех проектах; 2) список блокирующих и приоритетных задач.

defmodule APIs.Jira do
def issues(filter) do
end

def count(issues) do
end

defp username, do: Application.get_env(:sample_dashboard, :jira_username)
defp password, do: Application.get_env(:sample_dashboard, :jira_password)
defp url, do: Application.get_env(:sample_dashbaord, :jira_url)

defp authentication, do: ["Authorization": "Basic " <> Base.encode64("#{username}:#{password}")]
end

Как можно заметить по аргументам, функции count необходим список задач, так что для начала займёмся методом для обработки задач.

def issues(filter) do
  jql = URI.encode "filter=" <> to_string(filter) <> "+order+by+priority+DESC,updated+ASC"
  url = URI.parse url <> "/rest/api/2/search?maxResults=25&jql=" <> jql

  HTTPoison.get!(url, authentication).body
  |> Poison.decode!
end

Давайте же разберёмся, что здесь происходит. Прежде всего, значение, передаваемое в метод issues — это идентификатор JIRA-фильтра, который требуется определить из экземпляра JIRA.

Kitto

Переменная jql содержит необходимые данные интерфейса задач JIRA: фильтры, упорядоченные по приоритету (от высокого к низкому) и по времени создания задач (начиная с самых ранних). Стоит отметить, что в среде Confluence имеется гораздо большее количество опций для описания объектов ответа, но я не буду заострять на этом внимание. Составив запрос jql, поработаем над получением необходимого URL. Я присвоил значение 25 переменной maxResults, чтобы ввести целесообразное ограничение на количество возвращаемых тикетов. Дашборды обычно выводят на экраны телевизоров или другие средства представления, в которых нет возможности реализовать полосу прокрутки.

Теперь, имея необходимый URL, посылаем в JIRA запрос GET, указывая в его заголовке информацию аутентификации. Далее расшифруем полученный ответ в формате JSON с помощью Poison.

Было бы ещё неплохо отследить такие ошибки со стороны JIRA, как превышение лимита по количеству запросов, ошибка аутентификации и т.п., но не буду сейчас засорять этим свой наглядный пример.

Описанный метод подсчёта задач достаточно удобный, ведь JIRA возвращает количество задач в одном из полей JSON-файла. Я бы советовал разработчикам для передачи существующего возвращаемого объекта в метод count вместо отправки запросов к API использовать написанный мной модуль:

def count(issues) do
  issues["total"]
end

И вот последний метод, который нужно добавить в APIs.Jira для форматирования задач, выводимых на дашборд:

def issue_for_dashboard(issue) do
  %{label: issue["fields"]["summary"], value: issue["key"]}
end

Вот мы и создали модуль для взаимодействия с JIRA, позволяющий получить все необходимые для дашборда данные. Можно проверить результаты его работы, отправив запрос JIRA API через iex:

iex> APIs.Jira.issues(10300)
%{...}
iex> APIs.Jira.issues(10300) |> APIs.Jira.count
32

Написать джоб, имея этот модуль, не составит труда. Создадим джоб в jobs/jira.exs:

use Kitto.Job.DSL

filters = [
  "high_priority": "10300", # <= All open issues with critical or higher priority
  "bugs": "10100" # <= All open issues that are bugs
]

Этот джоб пока ничего не умеет. Всё, что мы имеем на данный момент, — это возможность сохранить ID фильтра в понятном разработчику виде для тех случаев, когда он не работает с JIRA, а просто читает код. Можно написать отдельные джобы для отображения количества багов и списка задач с высоким приоритетом, но, по-моему, разумнее было бы вывести на экран список и количество как багов, так и высокоприоритетных задач:

Enum.each(filters, fn ({name, filter}) ->
  job name, every: {5, :minutes} do
    issues = APIs.Jira.issues(filter)

    list = %{items: issues["issues"] |> Enum.map(&APIs.Jira.issue_for_dashboard/1)}
    count = %{value: APIs.Jira.count(issues)}

    broadcast! to_string(name) <> "_list", list
    broadcast! to_string(name) <> "_count", count
  end
end))

Перебрав фильтры в цикле, получаем следующие доступные в виджетах на дашбордах источники данных:

  • high_priority_list;

  • high_priority_count;

  • bugs_list;

  • bugs_count.

Названия источников данных основаны на том, что именно передаётся в качестве первого аргумента функции broadcast!.

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

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

Теперь, когда потоковая передача данных в дашборд уже реализована, осталось только добавить виджеты. Создадим новый дашборд в dashboards/issues.html.eex:

<div class="gridster">
    <ul></ul>
</div>

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

<li data-row="1" data-col="1" data-sizex="2" data-sizey="2">
    <div class="widget-jira"
         data-source="high_priority_list"
         data-widget="List"
         data-unordered="true"
         data-title="High Priority Issues"
         data-moreinfo="Blocker and Critical"></div>
</li>

Так, в первой колонке первого ряда создастся виджет две единицы в длину и две в ширину. Внутри div можно видеть несколько опциональных пунктов создания виджета. В data-source указывается источник данных, которые будут подгружаться в виджет. data-widget содержит название того виджета, на который выводятся данные. Все встроенные в Kitto виджеты располагаются в директории widgets. Остальные пункты уже относятся к конкретному виджету List.

Для подсчёта багов воспользуемся встроенным виджетом Number:

<li data-row="1" data-col="3" data-sizex="1" data-sizey="1">
    <div class="widget-jira"
         data-source="bugs_count"
         data-widget="Number"
         data-title="Open Bugs"></div>
</li>

Как было сказано ранее, data-source содержит название списка данных, передаваемое в функцию broadcast! при выводе данных на виджет, название которого хранится в data-widget.

Двигаемся дальше

Мы создали дашборд для экрана телевизора в офисе, с помощью которого специалисты смогут отслеживать открытые задачи прямо со своих рабочих мест. В целом приведённая схема работает хорошо, но всё же можно провести кое-какие доработки — показать приоритеты в виджете списка задач и добавить возможность реагирования на поступающие задачи. Или, например, создав блокировщик в JIRA, вы, возможно, захотите сменить фон виджета списка задач на красный. В следующей статье я пробегусь по тому, как создать свои собственные виджеты, с помощью Kitto React.

© 2020 / Россия Любые мысли и вопросы пишите на elixir@wunsh.ru.