Когда я устраивалась в Skyeng, солнце светило чуть ярче, трава зеленее не была (шла такая же ранняя весна), а тимлид попросил записывать в Jira, сколько времени ушло на кодинг, а сколько на разговоры и ревью. Хотя бы раз в две недели.
«По этим данным мы пробуем понять, надо ли корректировать эстимейты и нет ли проблем в коммуникации в команде», — говорили они. А вот кто такой «бабайка», так и не рассказали..
Поскольку мы все удалёнщики, идея звучала разумно. Да и мне стало интересно, куда девались эти восемь часов: вот прошли, но за чем именно? Однако логировать было непривычно. И вообще лень. Тогда я решила поискать что-нибудь, что будет вести ворклоги за меня. А в процессе исследования немного увлеклась и написала свой плагин для IntelliJ IDEA.
Ниже вы найдете субъективный обзор готовых инструментов и мой велосипед (с исходниками).
Первое, что я сделала — пошла узнавать у коллег, кто чем пользуется. Варианты подобрались примерно следующие:
Затрекать время на кодревью или написание доки? Плагин добавит кнопку (довольно ненавязчивого серого пакмена) почти куда угодно.
Так смотрится в Google Docs
И на GitHub
Натреканным можно полюбоваться на дашборде, а можно импортировать в таблицу и закинуть в Jira.
Интеграция в Jira поставляется отдельно, тоже браузерным расширением
Впрочем, есть возможность загрузить в свой рабочий календарь таблицу с ворклогами, полученную из Clockify. А для учёта времени в оффлайне есть десктопные приложения под windows, linux и mac. Мобильные приложения — тоже. Основной функционал бесплатен, но есть и несколько тарифных планов с плюшками вроде приватности, напоминалок и темплейтов для проектов.
Всё примерно то же самое, но плюшек чуть побольше — например, в десктопном приложении можно поставить себе напоминалки о логировании времени, включить режим pomodoro, есть даже возможность увязать определённое приложение с определённым проектом и настроить автотрекинг.
Но есть нюанс: меня уже на первый день использования подбешивало и это окошко, и уведомления с напоминаниям. Хотя в теории звучало клёво)
Изобретение моего коллеги. Как-то он решил сократить телодвижения по экспорту таблиц в Jira и переложил выгрузку налогированного в Toggl на скрипт. Можно положить в cron и радоваться жизни.
Логирует всё сам. Поэтому первое, о чём следует вспомнить, подключая его браузерное расширение — блэклист.
Помимо расширений, предоставляет несколько интеграций и приличное количество плагинов для наиболее распространённых IDE и редакторов.
Плагины для IDE трекают время, проведённое как в определённой git-ветке, так и в определённом файле. На скрине список доступных — весьма впечатляет. Серенькие — “в планах”, за их скорейшую реализацию можно проголосовать. Некоторые плагины в платной подписке за 9$ в месяц.
Кроме того, на дашборде можно увидеть, сколько времени в процентном соотношении потрачено на написание кода на разных языках: если вспомнить, что обычный android-проект предполагает использование Kotlin, Java, Groovy и xml — это вполне себе обретает смысл). А если поработать с установленным плагином некоторое время, можно заметить, что он довольно безжалостен: в дашборд попадает время активного чтения и написания кода. И не попадает залипание в монитор со стеклянным взглядом. Что и говорить, на любителя.
Возможно, пушка для стрельбы картошкой и то была бы полезнее, но чего не сделаешь в исследовательских целях) Помедитировав на исходники WakaTime, я решила сделать свой плагин, способный отслеживать активность в IDE, логировать время на его написание, а ещё отсылать всё это в Jira (в нашем флоу ветки называются по именам задач в Jira, поэтому выглядит вполне реалистично).
А чтобы не засыпать сервер запросами во время работы над кодом, логи мы будем отправлять при закрытии Android Studio, а до этого момента хранить локально.
Не используем Сomponents (это легаси), ранее представлявшие собой основные структурные единицы плагина. Могут быть уровня приложения, уровня проекта или уровня модуля. В исходниках WakaTime есть ApplicationComponent, но им можно, исходному коду уже лет пять и он должен сохранять обратную совместимость. Компоненты имеют привязку к жизненному циклу уровня, с которым они связаны. Так, например, ApplicationComponent загружается при старте IDE. При использовании будут блокировать перезапуск плагина без перезапуска IDE и вообще ведут себя неприятно. Поэтому вместо них лучше использовать services.
Используем Services — нынешние основные структурные единицы. Делятся на те же уровни приложения, проекта и модуля, помогут нам инкапсулировать логику на соответствующих уровнях и хранить состояние плагина.
В отличие от компонентов, Services нужно загружать самостоятельно, используя метод ServiceManager.getService(). Платформа гарантирует, что каждый сервис является синглтоном.
Добавляем Actions — могут быть шорткатом, дополнительным пунктом меню — словом, отвечают за всё, что как-то затрагивает пользовательские действия в IDE.
Используем Extensions — любое расширение функциональности, которое будет сложнее Actions: например, подвязаться к жизненному циклу IDE и выполнить что-то во время показа сплэшскрина или перед выходом.
Всё это должно быть объявлено в файлике /META_INF/plugin.xml.
Окно создания проекта. Мы будем писать наш плагин на Kotlin, а для сборки использовать Gradle
Это — первое, что мы видим после создания заготовки плагина в IDEA (File -> New -> Project… -> Gradle -> IntelliJ Platform Plugin). Он содержит информацию о зависимостях плагина и его краткое описание. В нём должны быть объявлены компоненты плагина — ранее упомянутые components, services, actions и extensions. Никакой определённой точки входа не будет — ею станет пользовательское действие или событие жизненного цикла IDE, в зависимости от наших потребностей.
Нам понадобятся вот эти зависимости — ведь мы хотим написать плагин под студию и чтобы он умел работать с Git.
Git4Idea является плагином для виртуальной файловой системы (VCS) и предоставляет топики, благодаря которым мы позднее сможем прослушивать события git — такие, как чекаут, например.
Поскольку мы хотим ещё и того, чтобы плагин умел работать с Jira, подключим через Gradle любезно предоставленную библиотеку с rest-клиентом. Для этого добавим туда maven-репозиторий Atlassian:
И собственно библиотеки:
Здесь мы определяем интересующую нас версию IDE и путь к ней (о, если бы настоящая Android Studio запускалась и работала так же шустро, как её облегчённая версия для отладки плагинов):
А здесь, возможно, когда-нибудь напишем о новом, улучшенном функционале. Но не сейчас.
Чтобы что-то пушить в Jira, для начала нужно залогиниться. Логично сделать это прямо после открытия проекта. Похоже, нам нужен extension, и мы, не колеблясь, объявим его в plugin.xml:
и в нём покажем диалог.
UI-компоненты — это главным образом компоненты из Swing с некоторыми расширениями из platform sdk. Всё это с недавних пор обёрнуто в kotlin ui dsl. На момент написания в документации отмечено, что dsl может очень сильно меняться между мажорными версиями, поэтому используем его с лёгким опасением:
Создали диалог, показали его, получили учётные данные от Jira. Теперь надо их сохранить, и желательно — безопасно, насколько это возможно. На помощь в этом придёт PersistingStateComponent.
PersistingStateComponent умеет делать две вещи — saveState и loadState: сериализовывать и сохранять переданный ему объект и извлекать его из хранилища.
Куда именно он должен сохранять данные, он узнаёт из аннотации State. В документации упомянуты два самых простых варианта — указать свой файл или хранить всё в настройках воркспейса:
Поскольку у нас особый случай, обратимся к документации по хранению чувствительных данных.
Ещё один PersistingStateComponent будет заниматься хранением времени, залогированного для разных веток.
С логином в Jira разобрались. С хранением пользовательских данных — тоже. Теперь нужно как-то отследить событие чекаута на ветку, чтобы начать отсчёт времени.
IntelliJ Platform предоставляет возможность повесить Observer на интересующие нас события, подписавшись на их топики в messageBus нужного уровня. Вот дока, она довольно интересная.
Топик — это некоторый эндпойнт, представление события. Всё, что нужно сделать — подписаться и реализовать листенер, который должен обрабатывать происходящее.
Например, мне нужно слушать чекаут в Git…
… закрытие приложения (чтобы радостно залогировать время)…
… и сохранение документов (просто так, чтобы было).
Этого, в принципе, уже достаточно (если нет, то всегда можно написать свой). Но что, если мы захотим слышать малейший скролл и движение мыши?
Собирает всё, что происходит в открытых окнах редактора — редактирование, изменение видимой области, движения мыши — и позволяет подписаться на это в одном месте и сразу.
Теперь у нас есть всё необходимое. Мы можем выделить из текущей ветки имя задачи в Jira (если оно там есть), посчитать время, проведённое в работе над ней, и при выходе из студии незамедлительно дописать его в задачу.
Для запуска нужна актуальная Android Studio (3.6.1).
Для проверки работоспособности — git и ветка с именем, хотя бы отдалённо похожим на таск в Jira.
Чтобы плагин не просто выводил время в консоль, а трекал его — раскомментить соответствующую строчку в Heartbeat.kt.
— Блин, оно какое-то слишком злое. А если я не пишу код? А если я ковыряю в носу и напряжённо пытаюсь понять, что в нём происходит? В коде, в смысле. Ведь на это и уходит основное время.
— Тогда хотя бы скролль его.
— Так и до имитации бурной деятельности недалеко. И потом, я студию могу неделями не выключать и не перезагружать. Может, не надо нам такого счастья?
— Определённо не надо. Тогда нужен другой повод, а то мне понравилось писать плагины. И с VCS я ещё толком не разбиралась.
— А ты сама им пользуешься?
— Не-а. Я как-то руками приучилась логировать, незаметно для себя.
«По этим данным мы пробуем понять, надо ли корректировать эстимейты и нет ли проблем в коммуникации в команде», — говорили они. А вот кто такой «бабайка», так и не рассказали..
Поскольку мы все удалёнщики, идея звучала разумно. Да и мне стало интересно, куда девались эти восемь часов: вот прошли, но за чем именно? Однако логировать было непривычно. И вообще лень. Тогда я решила поискать что-нибудь, что будет вести ворклоги за меня. А в процессе исследования немного увлеклась и написала свой плагин для IntelliJ IDEA.
Ниже вы найдете субъективный обзор готовых инструментов и мой велосипед (с исходниками).
Изучаю решения, которые использует команда
Первое, что я сделала — пошла узнавать у коллег, кто чем пользуется. Варианты подобрались примерно следующие:
1. Clockify — одна кнопка, чтоб править всем
Затрекать время на кодревью или написание доки? Плагин добавит кнопку (довольно ненавязчивого серого пакмена) почти куда угодно.
Так смотрится в Google Docs
И на GitHub
Натреканным можно полюбоваться на дашборде, а можно импортировать в таблицу и закинуть в Jira.
Интеграция в Jira поставляется отдельно, тоже браузерным расширением
Впрочем, есть возможность загрузить в свой рабочий календарь таблицу с ворклогами, полученную из Clockify. А для учёта времени в оффлайне есть десктопные приложения под windows, linux и mac. Мобильные приложения — тоже. Основной функционал бесплатен, но есть и несколько тарифных планов с плюшками вроде приватности, напоминалок и темплейтов для проектов.
2. Toggl, просто Toggl
Всё примерно то же самое, но плюшек чуть побольше — например, в десктопном приложении можно поставить себе напоминалки о логировании времени, включить режим pomodoro, есть даже возможность увязать определённое приложение с определённым проектом и настроить автотрекинг.
Но есть нюанс: меня уже на первый день использования подбешивало и это окошко, и уведомления с напоминаниям. Хотя в теории звучало клёво)
3. Toggl + скрипт на Python: когда просто кнопки уже недостаточно
Изобретение моего коллеги. Как-то он решил сократить телодвижения по экспорту таблиц в Jira и переложил выгрузку налогированного в Toggl на скрипт. Можно положить в cron и радоваться жизни.
4. Wakatime — там, куда мы отправляемся, кнопки не нужны
Логирует всё сам. Поэтому первое, о чём следует вспомнить, подключая его браузерное расширение — блэклист.
Помимо расширений, предоставляет несколько интеграций и приличное количество плагинов для наиболее распространённых IDE и редакторов.
Плагины для IDE трекают время, проведённое как в определённой git-ветке, так и в определённом файле. На скрине список доступных — весьма впечатляет. Серенькие — “в планах”, за их скорейшую реализацию можно проголосовать. Некоторые плагины в платной подписке за 9$ в месяц.
Кроме того, на дашборде можно увидеть, сколько времени в процентном соотношении потрачено на написание кода на разных языках: если вспомнить, что обычный android-проект предполагает использование Kotlin, Java, Groovy и xml — это вполне себе обретает смысл). А если поработать с установленным плагином некоторое время, можно заметить, что он довольно безжалостен: в дашборд попадает время активного чтения и написания кода. И не попадает залипание в монитор со стеклянным взглядом. Что и говорить, на любителя.
Хмм, исходный код WakaTime открыт: можно попытаться понять, как он работает...
Это уже интереснее, правда? Вот и я не могла пройти мимо и выкачала себе исходники плагина для IDE от JetBrains.
Попробуем разобраться. Основная логика находится в WakaTime.java. Там мы можем увидеть, как плагин навешивает листенеры на редактирование и скролл документов, заполняет очередь из Heartbeat (), и через WakaTime CLI отправляет её на сервер. Логика расчёта времени остаётся за кадром, но, кажется, мы и сами в состоянии написать подобное.
Попробуем разобраться. Основная логика находится в WakaTime.java. Там мы можем увидеть, как плагин навешивает листенеры на редактирование и скролл документов, заполняет очередь из Heartbeat (), и через WakaTime CLI отправляет её на сервер. Логика расчёта времени остаётся за кадром, но, кажется, мы и сами в состоянии написать подобное.
Пишем свой велосипед по аналогии
Возможно, пушка для стрельбы картошкой и то была бы полезнее, но чего не сделаешь в исследовательских целях) Помедитировав на исходники WakaTime, я решила сделать свой плагин, способный отслеживать активность в IDE, логировать время на его написание, а ещё отсылать всё это в Jira (в нашем флоу ветки называются по именам задач в Jira, поэтому выглядит вполне реалистично).
А чтобы не засыпать сервер запросами во время работы над кодом, логи мы будем отправлять при закрытии Android Studio, а до этого момента хранить локально.
1. Какие сущности мы можем встретить в исходном коде плагина (и использовать в своём)?
Не используем Сomponents (это легаси), ранее представлявшие собой основные структурные единицы плагина. Могут быть уровня приложения, уровня проекта или уровня модуля. В исходниках WakaTime есть ApplicationComponent, но им можно, исходному коду уже лет пять и он должен сохранять обратную совместимость. Компоненты имеют привязку к жизненному циклу уровня, с которым они связаны. Так, например, ApplicationComponent загружается при старте IDE. При использовании будут блокировать перезапуск плагина без перезапуска IDE и вообще ведут себя неприятно. Поэтому вместо них лучше использовать services.
Используем Services — нынешние основные структурные единицы. Делятся на те же уровни приложения, проекта и модуля, помогут нам инкапсулировать логику на соответствующих уровнях и хранить состояние плагина.
В отличие от компонентов, Services нужно загружать самостоятельно, используя метод ServiceManager.getService(). Платформа гарантирует, что каждый сервис является синглтоном.
Добавляем Actions — могут быть шорткатом, дополнительным пунктом меню — словом, отвечают за всё, что как-то затрагивает пользовательские действия в IDE.
Используем Extensions — любое расширение функциональности, которое будет сложнее Actions: например, подвязаться к жизненному циклу IDE и выполнить что-то во время показа сплэшскрина или перед выходом.
Всё это должно быть объявлено в файлике /META_INF/plugin.xml.
2. Plugin.xml и build.gradle
Окно создания проекта. Мы будем писать наш плагин на Kotlin, а для сборки использовать Gradle
Это — первое, что мы видим после создания заготовки плагина в IDEA (File -> New -> Project… -> Gradle -> IntelliJ Platform Plugin). Он содержит информацию о зависимостях плагина и его краткое описание. В нём должны быть объявлены компоненты плагина — ранее упомянутые components, services, actions и extensions. Никакой определённой точки входа не будет — ею станет пользовательское действие или событие жизненного цикла IDE, в зависимости от наших потребностей.
Нам понадобятся вот эти зависимости — ведь мы хотим написать плагин под студию и чтобы он умел работать с Git.
<depends>Git4Idea</depends>
<depends>com.intellij.modules.androidstudio</depends>
Git4Idea является плагином для виртуальной файловой системы (VCS) и предоставляет топики, благодаря которым мы позднее сможем прослушивать события git — такие, как чекаут, например.
Поскольку мы хотим ещё и того, чтобы плагин умел работать с Jira, подключим через Gradle любезно предоставленную библиотеку с rest-клиентом. Для этого добавим туда maven-репозиторий Atlassian:
repositories {
mavenCentral()
maven {
url "https://packages.atlassian.com/maven/repository/public"
}
}
И собственно библиотеки:
implementation "joda-time:joda-time:2.10.4"
implementation("com.atlassian.jira:jira-rest-java-client-core:4.0.0") {
exclude group: 'org.slf4j'
dependencies {
implementation "com.atlassian.fugue:fugue:2.6.1"
}
}
Здесь мы определяем интересующую нас версию IDE и путь к ней (о, если бы настоящая Android Studio запускалась и работала так же шустро, как её облегчённая версия для отладки плагинов):
intellij {
version '2019.1'
plugins 'git4idea'
alternativeIdePath 'E:\\Android Studio'
}
А здесь, возможно, когда-нибудь напишем о новом, улучшенном функционале. Но не сейчас.
patchPluginXml {
changeNotes """
Add change notes here.<br>
<em>most HTML tags may be used</em>"""
}
3. Создание UI
Чтобы что-то пушить в Jira, для начала нужно залогиниться. Логично сделать это прямо после открытия проекта. Похоже, нам нужен extension, и мы, не колеблясь, объявим его в plugin.xml:
<postStartupActivity implementation="Heartbeat"/>
и в нём покажем диалог.
UI-компоненты — это главным образом компоненты из Swing с некоторыми расширениями из platform sdk. Всё это с недавних пор обёрнуто в kotlin ui dsl. На момент написания в документации отмечено, что dsl может очень сильно меняться между мажорными версиями, поэтому используем его с лёгким опасением:
override fun createCenterPanel(): JComponent? {
title = "Jira credentials"
setOKButtonText("Save")
return panel {
row {
JLabel("Jira hostname")()
hostname = JTextField("https://")
hostname()
}
row {
JLabel("Username:")()
username = JTextField()
username()
}
row {
JLabel("Password:")()
password = JPasswordField()
password()
}
}
}
Создали диалог, показали его, получили учётные данные от Jira. Теперь надо их сохранить, и желательно — безопасно, насколько это возможно. На помощь в этом придёт PersistingStateComponent.
4. Устройство PersistingStateComponent (не очень сложное)
PersistingStateComponent умеет делать две вещи — saveState и loadState: сериализовывать и сохранять переданный ему объект и извлекать его из хранилища.
Куда именно он должен сохранять данные, он узнаёт из аннотации State. В документации упомянуты два самых простых варианта — указать свой файл или хранить всё в настройках воркспейса:
@Storage("yourName.xml")
@Storage(StoragePathMacros.WORKSPACE_FILE)
@State(
name = "JiraSettings",
storages = [
Storage("trackerSettings.xml")
])
Поскольку у нас особый случай, обратимся к документации по хранению чувствительных данных.
override fun getState(): Credentials? {
if (credentials == null) {
val credentialAttributes = createCredentialAttributes()
credentials = PasswordSafe.instance.get(credentialAttributes)
}
return credentials
}
override fun loadState(state: Credentials) {
credentials = state
val credentialAttributes = createCredentialAttributes()
PasswordSafe.instance.set(credentialAttributes, credentials)
}
Ещё один PersistingStateComponent будет заниматься хранением времени, залогированного для разных веток.
С логином в Jira разобрались. С хранением пользовательских данных — тоже. Теперь нужно как-то отследить событие чекаута на ветку, чтобы начать отсчёт времени.
5. Подписываемся на ивенты
IntelliJ Platform предоставляет возможность повесить Observer на интересующие нас события, подписавшись на их топики в messageBus нужного уровня. Вот дока, она довольно интересная.
Топик — это некоторый эндпойнт, представление события. Всё, что нужно сделать — подписаться и реализовать листенер, который должен обрабатывать происходящее.
Например, мне нужно слушать чекаут в Git…
subscribeToProjectTopic(project, GitRepository.GIT_REPO_CHANGE) {
GitRepositoryChangeListener {
currentBranch = it.currentBranch
}
}
… закрытие приложения (чтобы радостно залогировать время)…
subscribeToAppTopic(AppLifecycleListener.TOPIC) {
object: AppLifecycleListener {
override fun appWillBeClosed(isRestart: Boolean) {
if (!isRestart) {
JiraClient.logTime(entry.key, DateTime.now(), entry.value)
}
}
}
}
… и сохранение документов (просто так, чтобы было).
subscribeToAppTopic(AppTopics.FILE_DOCUMENT_SYNC) {
CustomSaveListener()
}
Этого, в принципе, уже достаточно (если нет, то всегда можно написать свой). Но что, если мы захотим слышать малейший скролл и движение мыши?
6. EditorEventMulticaster
Собирает всё, что происходит в открытых окнах редактора — редактирование, изменение видимой области, движения мыши — и позволяет подписаться на это в одном месте и сразу.
Теперь у нас есть всё необходимое. Мы можем выделить из текущей ветки имя задачи в Jira (если оно там есть), посчитать время, проведённое в работе над ней, и при выходе из студии незамедлительно дописать его в задачу.
7. Код и подробности здесь
Для запуска нужна актуальная Android Studio (3.6.1).
Для проверки работоспособности — git и ветка с именем, хотя бы отдалённо похожим на таск в Jira.
Чтобы плагин не просто выводил время в консоль, а трекал его — раскомментить соответствующую строчку в Heartbeat.kt.
8. Полезные ссылки
- Цикл статей о разработке плагинов
- Ещё одна интересная статья о разработке плагина от Android-разработчика
P.S. А что выбрала в итоге?
— Блин, оно какое-то слишком злое. А если я не пишу код? А если я ковыряю в носу и напряжённо пытаюсь понять, что в нём происходит? В коде, в смысле. Ведь на это и уходит основное время.
— Тогда хотя бы скролль его.
— Так и до имитации бурной деятельности недалеко. И потом, я студию могу неделями не выключать и не перезагружать. Может, не надо нам такого счастья?
— Определённо не надо. Тогда нужен другой повод, а то мне понравилось писать плагины. И с VCS я ещё толком не разбиралась.
— А ты сама им пользуешься?
— Не-а. Я как-то руками приучилась логировать, незаметно для себя.