Создание дашбордов на 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.

Переменная 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.