Atlassian Confluence: расширяем на python
- вторник, 30 июля 2019 г. в 00:20:10
В Альфастраховании мы активно пользуемся "Вики", движком которого выступает Atlassian Confluence. Когда я первый раз с ним всерьез столкнулся (в попытке создать в нем контент), мне в нем не хватило "динамичности" — хотелось иметь возможность программно формировать части страниц, взаимодействовать с другими системами и т.п.
Некоторое время бился головой в разные стены, но потом увидел, что "в доме не было одной стены". Хочу поделиться опытом — как можно добавить динамики в Confluence. Надеюсь, это будет полезно тем, кто им пользуется. И, как обычно, всем любознательным.
Изначально Confluence предполагает один универсальный способ расширения своей фукнциональности — плагины. Наверное, он хорош, есть один лишь недостаток — высоченный порог вхождения (нужно много чему научиться).
После непродолжительного (по космическим меркам) размышления нашлось еще два достаточно простых способа расширения его функциональности: стандартные макро "HTML" и "HTML Include", в этой статье я более подробно остановлюсь на последнем.
Принцип действия у этих способов один — в страницу Confluence встраивается HTML код, он может быть статическим (что тоже может быть интересным, например, для нестандартного форматирования страниц), может быть динамическим (включая серверные компоненты).
Давайте рассмотрим предложенный способ расширения возможностей Confluence на простом примере — списке согласования документа.
Что мы хотим сделать: добавить на страницу список согласования с тем, чтобы всем было видно — кто должен согласовать документ, был ли он согласован и, если был, то когда.
Для удобства сделаем элемент списка кнопкой и напишем в ней фамилию согласующего. Кнопка будет активной в случае, если страницу просматривает согласующий, и не активной во всех остальных случаях.
После согласования кнопка "перестанет быть кнопкой" — вместо кнопки после согласования отрисуем на странице факт согласования в виде текста (фамилия согласующего и дата согласования). В самом черновом варианте (без оформления) может выглядеть как-то так:
Комментарии — как это работает:
Немного сложно словами, давайте попробуем кодом прояснить.
Итак нам нужно создать два скрипта
Давайте создадим их, начнем с кнопки.
Схематично, как работает скрипт "кнопка":
date = getSignDate()
if date:
# отрисвываем текст с датой согласования и пр.
else:
if user==signee:
# отрисовываем активную кнопку
else:
# отрисовываем inactive кнопку
Скрипт должен представлять из себя валидный HTML документ, тело которого в самом минимальном варианте представляет из себя форму
Примерный вид скрипта (для краткости выкинул все лишнее — см. полный работающий код на github)
form = cgi.FieldStorage()
signee = form["signee"].value # логин согласующего (кто должен согласовать)
actual = form["actual"].value # логин текущего пользователя
id = form["id"].value # имя страницы, которую необходимо согласовать
resHtml = """
<!DOCTYPE HTML>
<html>
<head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head>
<body>
<form name="input" action="http://172.16.108.216/misc/sign_proc.py" method="get">
"""
server, token = wikiLogin() # логинимся в Confluence
userName = getUserName(token, signee) # получаем имя текущего пользователя по его логину
res = getSignDate(id, signee) # получаем дату согласования страницы
if res: # согласование было получено
resHtml += "<p>Согласовано ({0}, {1})</p>".format(userName, res) # отрисовываем текст
else: # согласования не было
if signee.lower() == actual.lower(): # текущий пользователь совпадает с согласующим - отрисовываем кнопку
resHtml += '<input type="hidden" name="id" value="{0}">'.format(id)
resHtml += '<input type="hidden" name="signee" value="{0}">'.format(signee)
resHtml += "Требуется Ваше согласование <input type=\"submit\" value=\"{0}\">".format(userName)
else: # текущий пользователь не есть согласующий - отрисовываем неактивную кнопку
resHtml += "Требуется согласование пользователем <input disabled type=\"submit\" value=\"{0}\">".format(userName)
resHtml += "</form></body></html>"
# выводим собранный HTML
sys.stdout.buffer.write(b'Content-Type: text/html;charset=utf-8\n\n')
sys.stdout.buffer.write(resHtml.encode("utf-8"))
Задача обработчика очень проста — зафиксировать факт нажатия кнопки "согласовать". После чего перенаправить обратно на страницу, откуда пришел запрос — при отрисовке страницы уже будет учтен факт согласования (см. описание выше).
Пример (сокращенный) скрипта "обработчика" (полный вариант — см. github)
form = cgi.FieldStorage()
signee = form["signee"].value # логин согласующего
id = form["id"].value # имя страницы, которую необходимо согласовать
addSignDate(id, signee) # фиксируем факт согласования
resHtml = """
<html>
<head>
<meta http-equiv="refresh" content="5;url=http://wiki.alfastrah.ru/display/DIT/{0}">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Согласование отработано</title>
</head>
<body>
<p>Запрос на согласование страницы <b>{0}</b> пользователем <b>{1}</b> обработан.
<br>Сейчас Вы будете перенаправлены обратно на страницу...</p>
</body>
</html>
""".format(id, signee)
sys.stdout.buffer.write(b'Content-Type: text/html;charset=utf-8\n\n')
sys.stdout.buffer.write(resHtml.encode("utf-8"))
Страница в Confluence содержит три макро HTML include с разными параметрами (см. код ниже) и в самом простом варианте выглядит так:
Код страницы в формате wiki markup — здесь можно видеть параметры
<h1>Важный документ</h1>
<p>Важный текст, требующий согласования</p>
<h1>Список согласования</h1>
<ul>
<li>
<ac:structured-macro ac:macro-id="..." ac:name="html-include" ac:schema-version="1">
<ac:parameter ac:name="url">
<ri:url ri:value="http://z14-0510-wiksap.vesta.ru/misc/sign_btn.py?signee=boss&actual=korolevmv&id=wikitools_sign"/>
</ac:parameter>
</ac:structured-macro>
</li>
<li>
<ac:structured-macro ac:macro-id="..." ac:name="html-include" ac:schema-version="1">
<ac:parameter ac:name="url">
<ri:url ri:value="http://z14-0510-wiksap.vesta.ru/misc/sign_btn.py?signee=korolevmv&actual=korolevmv&id=wikitools_sign"/>
</ac:parameter>
</ac:structured-macro>
</li>
<li>
<ac:structured-macro ac:macro-id="..." ac:name="html-include" ac:schema-version="1">
<ac:parameter ac:name="url">
<ri:url ri:value="http://z14-0510-wiksap.vesta.ru/misc/sign_btn.py?signee=maxvar&actual=korolevmv&id=wikitools_sign"/>
</ac:parameter>
</ac:structured-macro>
</li>
</ul>
Для того, чтобы описанная схема заработала необходимо учесть следующее
Иногда администраторы его "прячут" — уж очень много дополнительных хлопот он доставляет (обратная сторона возможностей).
Этот макрос является бесплатным и входит в Confluence — если у Вас его нет, обратитесь к администраторам, пусть поищут...
Confluence не будет выполнять скрипты, размещенные на неизвестных источниках, хост, на котором размещен скрипт должен быть в так называемых "белых списках" (обратитесь к админам). Это относится только к скрипту "кнопки" — скрипт обработчика вызывается уже кнопкой, на нее ограничения Confluence не действуют.
Скрипты (кнопки и обработчика) должны быть выполнимыми — скопируйте урл в браузер, он должен отработать и сформировать HTML, если этого не произошло — отлаживайтесь.
Если любой из скриптов "падает" с ошибкой — Вы не увидите ничего хорошего, отладьтесь как следует перед публикацией в Confluence.
Пытливый ум мог обратить внимание на параметры скриптов — с одной стороны, они избыточны (например, имя страницы, на которой размещена кнопка согласования — она известна, зачем ее заполнять?). С другой стороны — небезопасны ("злоумышленник" в нашем примере может изменить имя согласующего на свое или удалить кнопку согласования вовсе).
Избыточность можно "побороть" пользовательскими макросами (я долгое время удивлялся — чем они могут быть практически полезны? Напишу кратко в отдельной статье). Безопасность в Confluence обеспечивается историей страницы — все "ходы" записаны, можно что угодно менять, в Confluence остается замечательный "аудит след", позволяющий детально разобраться — кто что и когда менял.
В нашей практике были случаи, когда для сбора данных приходилось парсить код страницы (например, так у нас работало управление портфелем проектов). Это возможно и даже не слишком сложно. Эволюционно мы пришли к тому, что если какая-то информация на странице нужна только для кода, который эту информацию проинтерпретирует, то эту информацию проще сразу формулировать в самом коде (в виде JSON-а например): редактировать ее в режиме редактирования страницы достаточно просто, отрисовывается она программой, а вот экономия на парсинге и повышение надежности получаются существенными.
Для чего еще мы использовали эти макросы (как идеи — вдруг что-то окажется полезным), очень кратко
Оформление страницы: если хочется оформить страницу нестандартно — размечаем с помощью CSS, который содержится в HTML блоке
Календарь отпусков: используем какой-нибудь простой JS календарь, наполняем его данными из JSON, который редактируем прямо в коде страницы, получаем красивую табличку с отпусками в разбивке (год, месяц, неделя).
Печать карточек задач для скрам-доски: задаем в форме прямо на вики странице (в HTML блоке) параметры (номера задач в Jira и дополнительная атрибутика), серверная часть формирует карточки в виде, пригодном для отправки на принтер.
Управление портфелем проектов: была у нас и такая затея. Прямо в вики вели скор карты (скор карта — это специальным образом размеченная страница), по этим скор картам собирали крупноблочный план, который красиво отрисовывался в виде гант-диаграммы.
Глоссарий: возможность визуализировать термины — пояснения к отдельным словам страницы — из общего словаря и выдавать словарные статьи в виде "попапов". Сам словарь автоматически собирается в нижней части страницы.
Графики: если нужно отрисовать какой-то несложный график, то очень просто это сделать, встроив HTML блок с каким-либо из стандартных пакетов (Google Charts, HighCharts, etc)
Таблицы: поверх любой таблицы в Confluence можно "повесить" Datatable — получаем фильтруемую таблицу с "пагинацией", очень красиво и удобно.
Список можно продолжать достаточно долго, в процессе эксплуатации была замечено одно существенное неудобство: ошибки в серверном кода достаточно плохо ловятся — 500 ошибка плохо визуализируется и практически не содержит информации. В-остальном, экспериментируйте — это достаточно безопасно.
Описанный выше простой способ повышеия динамичности Confluence позволяет решить много разных задач. Для его использования не требуется особых инструментов и навыков. Использование достаточно безопасно. Рекомендую.
В следующей статье я расскажу об опыте использования пользовательских макро в Confluence — что, на мой взгляд, можно с их помощью действительно улучшить.
В программировании можно все — вопрос только времени и мотивации.