Как связать админку продукта и его базу знаний (и обойтись без доработок продукта)
- вторник, 11 октября 2022 г. в 00:40:57
Всем привет! Сталкивались ли вы с ситуацией, когда в вашей админке пользователям трудно интуитивно разобраться, возможности быстро исправить это нет, а существующую документацию никто не читает? Знакомы ли вам частые вопросы вида "А как это настроить?" или "А можно ли сделать настройками X?", ответы на которые уже описаны?
С подобным столкнулся я, и подумал, а что легко и быстро можно попробовать в качестве эксперимента. В статье расскажу вариант решения, как все это сделать без доработок самого продукта, что у меня в итоге вышло (и что не вышло), какие ошибки допустил и как можно сделать лучше.
Все началось очень просто (и довольно давно - описанное происходило в начале этого года).
В продукте, где я работаю, есть административная панель, или же попросту админка. И этой админкой пользуются сотрудники компании при выполнении разного вида настроек. А поскольку необходимостей настроить что-то много, то и действий там производится немало. И все бы ничего, но не все настройки такие простые, не весь интерфейс полностью очевиден, да и вообще с большой силой приходит большая ответственность с большой гибкостью приходит бОльшая сложность настроек.
Соответственно у нас были довольно частыми вопросы вида "А как что-то здесь настроить?", "А что делает эта кнопка?", "А можно ли настройками сделать вот так?" и прочее. При всем этом у нас была и наша внутренняя база знаний в Confluence. Далее мы улучшили и ее, но на тот момент она была еще в процессе донаполнения и причесывания. Статьи были не конца упорядочены, но тем не менее, достаточно большое количество материалов уже имелось.
Из абзаца выше нам видно две ключевые проблемы:
Админка продукта сейчас не позволяет интуитивно разобраться в ней и не нуждаться в дополнительной информации для работы;
Пользователи не обращаются к статьям в базе знаний за информацией.
Можем ли быстро исправить первую из них? Кажется, нет. В целом мы развиваем и улучшаем нашу административную панель, однако это отдельный фронт работ, требующий ресурсов и времени.
А что насчет второй?
Очевидно, что, несмотря на наличие в базе знаний статей, ими пользовались далеко не всегда и не все. Я видел следующие причины тому:
Незнание того, что статья про это есть вообще;
Лень заходить в Confluence и искать нужную статью, потому что это долго/неудобно;
Нелюбовь к статьям вообще и нежелание их читать (допускаем, что и такое бывает).
И если с третьим пунктом мы здесь пока ничего поделать не можем, то первые два попробуем поправить. По тому, как создавать базу знаний, наполнять ее и делать так, чтобы ей реально пользовались, есть отдельные статьи, а сегодня я хочу поговорить об еще одном интересном шаге, который может повысить ее ценность и добавить удобства пользователям.
Сформулируем образ результата: хотим сделать так, чтобы пользователи могли быстрее и эффективнее выполнять свои задачи в административной панели, используя существующие материалы в базе знаний.
Какую метрику успеха мы можем использовать? Мне показалось, что простой и подходящей будет количество вопросов по работе в админке в рабочих чатах, и снижение этого самого количества покажет, что мы сделали верный шаг.
Вводные:
Нужно, чтобы пользователи при работе с нашей админкой могли быстрее разобраться, как выполнить в ней свою задачу;
Имеем пул статей по возможностям и принципам работы админки в Confluence, эти статьи могут располагаться в разных местах и быть труднодоступными;
Наше решение должно быть максимально простым и дешевым (помним, что это пока только наша гипотеза);
Наше решение должно быть легкодоступным для пользователей разного уровня подготовки;
Нам надо обойтись без доработки продукта вообще.
Откуда пятый пункт? Здесь надо сказать, что в компании я не выполняю роль разработчика, в продукт своими руками изменения не вношу (и не должен). В продуктовых планах подобного не было, поэтому решил сделать сам MVP, обкатать его, а уже в случае успеха предложить перенести идею более организованно и качественно в сам продукт.
Для начала скажу, что для меня задача расширения функциональности "чужих" веб-приложений снаружи является не является новой. Обычно для этого я использую UserJS (пользовательские скрипты).
UserJS - это сторонний (не являющийся частью искомого сервиса) JavaScript-код, выполняющийся в браузере в контексте страницы (обычно до ее полной загрузки).
Внедряя свой скрипт на страницу мы можем самостоятельно доработать ее под свои нужды. В последние годы про это уже и не говорят, однако, если вспомнить, более десяти лет назад они были достаточно популярны, особенно в Опере.
В чем минусы использования UserJS в данном случае?
Для реализации интерфейса взаимодействия с пользователем нам бы пришлось взаимодействовать с существующими элементами страницы и что-то в них менять. А это означает, что при изменениях на фронтенде у пользователей может не только сломаться сам скрипт, но и могут появиться еще какие-нибудь баги в штатной работе админки, вызванные им. Это приведет к тому, что пользователи пойдут жаловаться на баги, которых на самом деле в продукте нет, что создаст лишнюю бесполезную работу.
Для его использования нужно устанавливать какое-либо дополнительное расширение, добавлять туда скрипт - звучит сложновато, да и есть простор для того, чтобы совершить ошибку.
В случае каких-либо необработанных ошибок мы еще и будем гадить из JS-консоли в свой собственный Sentry и смущать честных фронтенд-разработчиков тем, что они видят ошибки в несуществующем для них коде.
Поэтому я подумал, а что еще используется для той же цели? Ответ пришел быстро - браузерные расширения.
Распакованное расширение можно легко поставить локально.
Расширение не будет взаимодействовать с содержимым страницы, а значит конфликты в эту сторону исключены.
Расширение так же легкодоступно и находится в пределах клика.
К тому же до этого мне не доводилось создавать браузерные расширения, и я решил, что будет интересным опытом разобраться в этом и познакомиться с ними поближе. В данном случае у нас появляется одно ограничение, которое мы принимаем: наше расширение будет заточено под один конкретный движок браузеров - Chromium.
Раз до этого расширения я не создавал, то первично решил разобраться, а что вообще для этого нужно.
Google сам говорит нам, что для основа для расширения - файл manifest.json. В нем мы задаем основной конфиг нашего расширения: название, описание, иконки, права доступа и прочее. Кроме этого, у нас есть базовые возможность использования логики, из них нас интересует popup - та логика, которую пользователь может использовать, взаимодействуя с расширением, - нажимая на него.
Для нас подходящим видится использование popup-части, поскольку участие пользователя мы ожидаем и взаимодействовать с DOM'ом страницы не хотим. Фоновые скрипты нам пока ни к чему, поэтому их опустим, и получим минимальную начальную файловую структуру вида:
manifest.json
popup.html
popus.js
Наполним наш manifest.json:
{
"name": "Make admin panel great again",
"description": "Ссылки на документацию по разделам в Confluence",
"version": "1.0",
"manifest_version": 3,
"icons": {
"16": "16.png",
"48": "48.png",
"128": "128.png"
},
"host_permissions": [
"<all_urls>"
],
"permissions": [
"activeTab"
],
"action": {
"default_popup": "popup.html"
}
}
По формату файла имеется исчерпывающая документация, поэтому подробно останавливаться на нем не буду.
С расширением начально разобрались, теперь надо продумать, как именно мы его будем делать.
Наша админка имеет разные разделы, и в зависимости от того, где сейчас пользователь находится, ему актуальны будут разные статьи. Попробуем изначально простой принцип: по клику на иконку будем показывать пользователю список статей, которые подходят для данного раздела.
Звучит здорово, но что со списком статей? В начальной версии, допустим, соберем его руками, но где его хранить? В самой первой итерации я сделал его локальным - то есть лежащим рядом с прочими файлами расширения, но в таком варианте мне сразу не понравилось, что у каждого пользователя будет своя копия, и мы не сможем централизованно управлять изменениями. Решение я выбрал простое - сделал конфиг удаленным, разместил его у себя на сервере, а в расширение он уже подтягивался "на лету", а со стандартными возможностями HTTP-кэширования работает довольно шустро.
Сперва надо понять, как мы можем определить, в каком разделе сейчас находится пользователь, для того чтобы показать ему соответствующие статьи.
Все названия и URL-адреса изменены и являются вымышленными.
В случае нашего продукта это можно сделать на основе URL-адресов, а точнее их паттерна. Пусть вся наша админка находится по адресу /panel/.*
.
Введем два понятия, они нам далее помогут:
Главная секция - первичный раздел: company, global, admin;
Секция - раздел с определенной функциональностью, идет после главной секции.
При этом у нас есть три типа главных секций:
настройки компаний - /panel/company/{company_id}/{section}/.*
общие настройки - /panel/company/global/{section}/.*
администрирование системы - /panel/{section}/.*
Значит, на основе пути URL мы сможем первично определить тип главной секции. Что касается самих секций - каждая из них имеет свое название, при этом названия могут пересекаться между типами главных секций.
Здесь надо отметить, что я в момент реализации не подумал развязать такую связь многие-ко-кногим и избавиться от нее. Поэтому в приведенном примере имеем ее.
Разделы идут после главной секции, а за ними может лежать еще какой-то контент. Так, например, пользователю, который находится по адресу /panel/company/1/users/.*
мы хотим показать все статьи, которые связаны с работой с пользователями.
На момент разработки часть информации в текущей структуре базы знаний была не разделена: одна и та же статья могла быть актуальная для работы с секцией в разных местах, поэтому я решил начально определять статьи исходя из секции, а на основании главной секции уже определять, есть ли в ней эта секция или нет.
Изобразим небольшое дерево решений, которое позволяет более наглядно увидеть принцип.
Разработку вел на ванильном JS.
В popup.js для начала работы мы должны получить URL текущей активной вкладки. Здесь важно отметить, что из расширения у нас просто так нет доступа к объекту window текущей вкладки, для работы с ней нужно использовать API, предоставляемый Chrome:
let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (tab?.url === undefined) {
return false;
}
Проверим полученный URL на предмет того, что мы действительно в нашей админке. Такая реализация обусловлена тем, что в моем случае, кроме продуктивного окружения, необходима была работа еще и в других, находящихся на поддоменах.
let url = new URL(tab.url);
let hostSplitted = url.hostname.split('.');
let pathnameSplitted = url.pathname.split('/');
if (pathnameSplitted[1] !== 'panel'
|| hostSplitted[hostSplitted.length - 1] !== "com"
|| hostSplitted[hostSplitted.length - 2] !== "site"
) {
throw new Error('wrongURL');
}
Далее определим на основании URL главную секцию и секцию, помня наш разбор паттернов URL выше.
let adminSectionsArray = ['companies', 'users',]; // array of all the admin sections availiable
...
function getMainSectionType(pathnameSplitted) {
// for /panel/company/1/something
if (pathnameSplitted[2] === 'company' && Number.isInteger(+pathnameSplitted[3])) {
return 'company';
}
// for /panel/company/global/something
else if (pathnameSplitted[2] === 'company' && pathnameSplitted[3] === 'global') {
return 'global';
}
// for /panel/something
else if (adminSectionsArray.includes(pathnameSplitted[2])) {
return 'admin';
}
else return false;
}
function getSection(pathnameSplitted, mainSectionType) {
switch (mainSectionType) {
case 'company':
return pathnameSplitted[4];
case 'global':
return pathnameSplitted[4];
case 'admin':
return pathnameSplitted[2];
default:
return false;
}
}
Время работы с конфигом для хранения списка статей. Сам конфиг реализуем в виде JSON, содержащего на верхнем уровне ключи с названиями секций (совпадающие с тем, как секции называются в URL), а в качестве значения каждого из ключей - массив объектов со статьями. Конфиг пока наполняем вручную.
{
"users": [
{
"title": "Управление пользователями", // название статьи для отображения
"URL": "https://product.atlassian.net/wiki/spaces/test/pages/123456789" // ссылка на статью в базе знаний
},
...
],
...
}
Из удаленного конфига получаем доступные секции и массив статей для каждой из них.
В зависимости от главного типа у нас есть жестко определенный набор секций, который пока оставим на уровне расширения, т.к. он меняется реже (далее можем вынести также).
const CONFIG_URL = 'https://site.com/files/config.json'; // remote config URL
...
async function getSectionDocs(mainSectionType, section) {
let configData = await getConfig(CONFIG_URL);
let sectionVars = await configData.json();
let companySections = {
main: sectionVars?.main,
users: sectionVars?.users,
dictionaries: sectionVars?.dictionaries,
analytics: sectionVars?.analytics,
};
let globalSections = {
dictionaries: sectionVars?.dictionaries,
notifications: sectionVars?.notifications,
};
let adminSections = {
companies: sectionVars?.companies,
users: sectionVars?.users,
};
let sectionDocs;
switch (mainSectionType) {
case 'company':
sectionDocs = companySections[section];
break;
case 'global':
sectionDocs = globalSections[section];
break;
case 'admin':
sectionDocs = adminSections[section];
break;
default:
sectionDocs = false;
}
return sectionDocs;
}
Для отображения не используем ничего хитрого: выводим все статьи в список со ссылками "target=_blank"
, еще добавил прелоадер на время получения данных конфига с другого ресурса. Для страницы чуть подкрутил стили, чтобы смотрелась поопрятнее и добавил максимальную высоту-ширину, чтобы поп-ап не разъехался на весь экран. Получилось вот так:
Отдельно пару слов про обработку ошибок: включил только самый минимальный набор, который был показан выше в дереве решений: при клике на расширение на другом сайте, если раздел еще не поддерживается, если не можем получить конфиг, если для раздела не добавлены статьи.
Ну вот и все, наше расширение готово и работает. Исходники приложены в конце статьи.
Протестировав работу расширения на тот момент, я презентовал его коллегам, снабдив инструкцией по установке и использованию и предложил писать, если нужно будет обновить список статей в конфиге. Напомню, что идея была в том, чтобы проверить, будут ли люди вообще им пользоваться, насколько это зайдет и как повлияет на выбранную метрику.
Как обычно бывает, все порадовались и сказали: "О, круто". А что дальше?
Дальше мне было бы нужно суметь ответить хотя бы на вопросы: "Сколько пользователей попробовало решение в работе? Сколько продолжило использование?" (а еще было бы неплохо узнать, почему кто-то не попробовал или почему кто-то попробовал, но забросил). Когда мы работаем с маленькой выборкой пользователей, как было у меня, получить такие данные гораздо проще. Однако даже этого я не сделал. По факту я так и не узнал, пользовался ли кто-то расширением или нет. Я знал, что несколько людей как минимум установили его, и на этом все. Еще в самом начале у меня была мысль по использованию в расширении гугл-аналитики, но я, увы, подумал, что это необязательно и решил повременить.
Что там с нашей метрикой успеха? Фактически количество вопросов на дистанции не сократилось. их также продолжали задавать. Да и чтобы быть откровенным, я даже не попытался это численно замерить, а хотел опереться на "визуальное" изменение. Так что за этот пункт ставим тоже минус.
Далее со временем статьи в базе знаний стали сильно изменяться: менялись существующие, появлялись новые, менялась структура базы знаний. И, поскольку в течение этого времени ни один человек так ни разу и не обратился за обновлением конфига, я сделал предположение, что, скорее всего, реальных пользователей сейчас нет. Я решил удалить конфиг с сервера и проверить, заметит ли кто-то и придет ли спросить, что случилось и почему расширение не работает, но и в этот раз обращений не было.
В моем случае затея, конечно, похожа на провал, но настоящим провалом была, увы, моя работа по анализу результатов.
Даже для самого себя у меня нет рационального объяснения, почему я ничего не сделал и даже не попробовал подвести итоги, однако из данного примера постарался извлечь уроки (довольно банальные).
Запустив эксперимент (это касается в общем и других изменений) даже внутри компании, важно не забить забыть про мониторинг и анализ результатов. Поэтому полезно заранее придумать, как и на основании чего мы сможем определить успех/неудачу, по каким метрикам сможем что-то понять детальнее. В данном случае:
Если выборка действительно маленькая, то достаточно запустить небольшой опрос и попросить пользователей пройти его. Опросом можно будет и померить количество, и сразу в нем, либо после, собрать качественную обратную связь.
Можно подключить какую-то простую веб-аналитику, чтобы по крайней мере иметь количественные метрики использования. Можно даже связать данные с конкретным пользователем для более точной идентификации.
Поскольку, как описано выше, в моем случае затея оказалась неуспешной, дальнейшего развития история не увидела. Здесь приведу свои мысли под дальнейшие итерации, либо другие пути применения.
Собирать конфиг руками - круто, но кто захочет поддерживать его актуальность на постоянной основе? Поэтому уместным развитием может быть автоматизация формирования конфига со статьями. На примере Confluence это может быть реализовано, например, так:
Добавляем у статей какой-то признак, позволяющий однозначно связать статью с соответствующими разделами админки, например, метку(и).
Периодически обращаемся к API Confluence, получаем нужные нам статьи, обрабатываем данные и формируем конфиг.
Можно улучшить формат конфига: вынести туда связь главных секций и секций, заодно развязав их.
Если говорить про расширение, то можно пересмотреть подход к работе: еще в бэкграунде проверять доступные статьи для текущей страницы и в бейдже (badge) показывать, например, число доступных статей, чтобы уже до клика было понятно, есть ли статьи для такого раздела или нет.
Если ваш продукт внедряют партнеры-интеграторы, у них возникают подобные проблемы и при этом у вас есть база знаний для них, можно дать аналогичное решение им (на месте партнера я был бы очень рад такому, за неимением большего).
Если продукт какой-то нишевый и интерфейс действительно такой сложный, что сами пользователи без инструкций не могут в нем работать, как один из простых шагов на пути к улучшению их пользовательского опыта может быть подобная практика.
Конечно, описанный пример как есть не годится в качестве серьезного продуктового решения, однако его можно использовать в качестве быстрой проверки перед разработкой чего-то более серьезного. Было бы очень интересно услышать мнение других: сталкивались ли с похожими проблемами у себя, и если да, как решали.
Исходники на Github: https://github.com/anador/admin-docs-chrome-extension