В Альфастраховании мы активно пользуемся «Вики», движком которого выступает Atlassian Confluence. Когда я первый раз с ним всерьез столкнулся (в попытке создать в нем контент), мне в нем не хватило «динамичности» — хотелось иметь возможность программно формировать части страниц, взаимодействовать с другими системами и т.п.
Некоторое время бился головой в разные стены, но потом увидел, что «в доме не было одной стены». Хочу поделиться опытом — как можно добавить динамики в Confluence. Надеюсь, это будет полезно тем, кто им пользуется. И, как обычно, всем любознательным.
Динамичный вики
Изначально Confluence предполагает один универсальный способ расширения своей фукнциональности — плагины. Наверное, он хорош, есть один лишь недостаток — высоченный порог вхождения (нужно много чему научиться).
После непродолжительного (по космическим меркам) размышления нашлось еще два достаточно простых способа расширения его функциональности: стандартные макро «HTML» и «HTML Include», в этой статье я более подробно остановлюсь на последнем.
Принцип действия у этих способов один — в страницу Confluence встраивается HTML код, он может быть статическим (что тоже может быть интересным, например, для нестандартного форматирования страниц), может быть динамическим (включая серверные компоненты).
Пример: запрос на согласование
Давайте рассмотрим предложенный способ расширения возможностей Confluence на простом примере — списке согласования документа.
Что мы хотим сделать: добавить на страницу список согласования с тем, чтобы всем было видно — кто должен согласовать документ, был ли он согласован и, если был, то когда.
Для удобства сделаем элемент списка кнопкой и напишем в ней фамилию согласующего. Кнопка будет активной в случае, если страницу просматривает согласующий, и не активной во всех остальных случаях.
После согласования кнопка «перестанет быть кнопкой» — вместо кнопки после согласования отрисуем на странице факт согласования в виде текста (фамилия согласующего и дата согласования). В самом черновом варианте (без оформления) может выглядеть как-то так:

Схема взаимодействия

Комментарии — как это работает:
- страница в Confluence содержит несколько макро «html include» — фактически HTTP GET запросов с параметрами (рассмотрим подробнее ниже)
- при отрисовке страницы эти запросы (python скрипты) исполняются на сервере приложений, результат (сгенерированный HTML) отрисовывается на странице
- в процессе своей работы скрипт, например, обращается в базу данных с историей подписания страниц, если страница еще не была «подписана» подписантом — HTML будет содержать кнопку (все, как описано выше)
- при нажатии на кнопку происходит submit — вызов другого python скрипта (скрипта обработки факта согласования)
- этот скрипт сохраняет информацию о факте подписания в базу данных и перенаправляет пользователя обратно на исходную страницу (при отрисовки которой уже будет учтен факт подписания — нажатия на кнопку «подписать»)
Немного сложно словами, давайте попробуем кодом прояснить.
Итак нам нужно создать два скрипта
- скрипт формирования кнопки «подписать» (буду называть его скрипт «кнопка»)
- скрипт отработки факта подписания (реакция на нажатие кнопки — буду называть его скрипт «обработчик»)
Давайте создадим их, начнем с кнопки.
Скрипт «кнопка»
Схематично, как работает скрипт «кнопка»:
date = getSignDate() if date: # отрисвываем текст с датой согласования и пр. else: if user==signee: # отрисовываем активную кнопку else: # отрисовываем inactive кнопку
Скрипт должен представлять из себя валидный HTML документ, тело которого в самом минимальном варианте представляет из себя форму
- action формы содержит URL скрипта «обработчик»
- активная кнопка — это submit
- hidden поля формы содержат дополнительные параметры для обработчика (в нашем случае — имя страницы и логин подписанта)
Примерный вид скрипта (для краткости выкинул все лишнее — см. полный работающий код на 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
Страница в 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>
Немного о настройке
Для того, чтобы описанная схема заработала необходимо учесть следующее
Наличие макроса HTML Include
Иногда администраторы его «прячут» — уж очень много дополнительных хлопот он доставляет (обратная сторона возможностей).
Этот макрос является бесплатным и входит в Confluence — если у Вас его нет, обратитесь к администраторам, пусть поищут…
Белые списки Confluence
Confluence не будет выполнять скрипты, размещенные на неизвестных источниках, хост, на котором размещен скрипт должен быть в так называемых «белых списках» (обратитесь к админам). Это относится только к скрипту «кнопки» — скрипт обработчика вызывается уже кнопкой, на нее ограничения Confluence не действуют.
Выполнимость скриптов
Скрипты (кнопки и обработчика) должны быть выполнимыми — скопируйте урл в браузер, он должен отработать и сформировать HTML, если этого не произошло — отлаживайтесь.
Ошибки в скриптах
Если любой из скриптов «падает» с ошибкой — Вы не увидите ничего хорошего, отладьтесь как следует перед публикацией в Confluence.
Параметры скриптов
Пытливый ум мог обратить внимание на параметры скриптов — с одной стороны, они избыточны (например, имя страницы, на которой размещена кнопка согласования — она известна, зачем ее заполнять?). С другой стороны — небезопасны («злоумышленник» в нашем примере может изменить имя согласующего на свое или удалить кнопку согласования вовсе).
Избыточность можно «побороть» пользовательскими макросами (я долгое время удивлялся — чем они могут быть практически полезны? Напишу кратко в отдельной статье). Безопасность в Confluence обеспечивается историей страницы — все «ходы» записаны, можно что угодно менять, в Confluence остается замечательный «аудит след», позволяющий детально разобраться — кто что и когда менял.
Взаимодействие со страницей
В нашей практике были случаи, когда для сбора данных приходилось парсить код страницы (например, так у нас работало управление портфелем проектов). Это возможно и даже не слишком сложно. Эволюционно мы пришли к тому, что если какая-то информация на странице нужна только для кода, который эту информацию проинтерпретирует, то эту информацию проще сразу формулировать в самом коде (в виде JSON-а например): редактировать ее в режиме редактирования страницы достаточно просто, отрисовывается она программой, а вот экономия на парсинге и повышение надежности получаются существенными.
Еще примеры
Для чего еще мы использовали эти макросы (как идеи — вдруг что-то окажется полезным), очень кратко
Оформление страницы: если хочется оформить страницу нестандартно — размечаем с помощью CSS, который содержится в HTML блоке
Календарь отпусков: используем какой-нибудь простой JS календарь, наполняем его данными из JSON, который редактируем прямо в коде страницы, получаем красивую табличку с отпусками в разбивке (год, месяц, неделя).
Печать карточек задач для скрам-доски: задаем в форме прямо на вики странице (в HTML блоке) параметры (номера задач в Jira и дополнительная атрибутика), серверная часть формирует карточки в виде, пригодном для отправки на принтер.
Управление портфелем проектов: была у нас и такая затея. Прямо в вики вели скор карты (скор карта — это специальным образом размеченная страница), по этим скор картам собирали крупноблочный план, который красиво отрисовывался в виде гант-диаграммы.
Глоссарий: возможность визуализировать термины — пояснения к отдельным словам страницы — из общего словаря и выдавать словарные статьи в виде «попапов». Сам словарь автоматически собирается в нижней части страницы.
Графики: если нужно отрисовать какой-то несложный график, то очень просто это сделать, встроив HTML блок с каким-либо из стандартных пакетов (Google Charts, HighCharts, etc)
Таблицы: поверх любой таблицы в Confluence можно «повесить» Datatable — получаем фильтруемую таблицу с «пагинацией», очень красиво и удобно.
Список можно продолжать достаточно долго, в процессе эксплуатации была замечено одно существенное неудобство: ошибки в серверном кода достаточно плохо ловятся — 500 ошибка плохо визуализируется и практически не содержит информации. В-остальном, экспериментируйте — это достаточно безопасно.
Чуть подытожим
Описанный выше простой способ повышеия динамичности Confluence позволяет решить много разных задач. Для его использования не требуется особых инструментов и навыков. Использование достаточно безопасно. Рекомендую.
В следующей статье я расскажу об опыте использования пользовательских макро в Confluence — что, на мой взгляд, можно с их помощью действительно улучшить.
В программировании можно все — вопрос только времени и мотивации.
ссылка на оригинал статьи https://habr.com/ru/company/alfastrah/blog/460929/
Добавить комментарий