I am an Android developer and I didn’t like to do manual work.

When I settled in Skyeng, the sun was shining a little brighter, the grass was not greener (it was the same early spring), and the team leader asked me to write to Jira how much time was spent on coding, and how much talk and review. At least once every two weeks.


“Based on these data, we are trying to understand whether it is necessary to adjust the estimates and whether there are problems in communication in the team,” they said. But who such a “babayka” was never told. .

Since we are all remote workers, the idea sounded reasonable. And it became interesting to me where these eight hours had gone: here they were, but for what exactly? However, logging was unusual. And in general laziness. Then I decided to look for something that would keep working for me. And in the process of research, I got a little carried away and wrote my plugin for IntelliJ IDEA.

Below you will findsubjective review of finished tools and my bike (with source).

I study the solutions that the team uses


The first thing I did was go to find out from my colleagues who uses what. The options are approximately the following:

1. Clockify - one button to rule everything


Do you need time for coding or writing docks? The plugin will add a button (a fairly unobtrusive gray pacman) almost anywhere.


So it looks in Google Docs


And on GitHub

you can admire Natrekannyi on a dashboard, or you can import into a table and drop it into Jira.


Integration into Jira is supplied separately, also by browser extension.

However, it is possible to upload a table with worklogs obtained from Clockify to your working calendar. And for time tracking offline there are desktop applications for windows, linux and mac. Mobile apps too. The main functionality is free, but there are several tariff plans with goodies like privacy, reminders and templates for projects.

2. Toggl , just Toggl


Everything is about the same, but a little more goodies - for example, in a desktop application, you can set yourself reminders about time logging, turn on pomodoro mode, there is even the opportunity to link a specific application to a specific project and set up auto tracking.


But there is a nuance: on the first day of use I was amazed by this window and notifications with reminders. Although the theory sounded cool)

3. Toggl + Python script : when just a button is no longer enough


The invention of my colleague. Somehow, he decided to reduce the gestures for exporting tables to Jira and shifted the unloading of taxation in Toggl to a script. You can put in a cron and enjoy life.

4. Wakatime - where we go, no buttons needed


Logs everything himself. Therefore, the first thing to remember when connecting its browser extension is blacklist.

In addition to extensions, it provides several integrations and a decent number of plugins for the most common IDEs and editors.


Plugins for the IDE track the time spent both in a specific git branch and in a specific file. On the screen, the list of available ones is very impressive. Serenyk - “in the plans”, for their speedy implementation, you can vote. Some plugins in a paid subscription for $ 9 per month.

In addition, on the dashboard, you can see how much time is spent as a percentage on writing code in different languages: if you remember that a regular android project involves the use of Kotlin, Java, Groovy and xml - this makes sense to itself). And if you work with the installed plugin for a while, you will notice that it is rather ruthless: the dashboard gets the time of active reading and writing code. And sticking does not get into the monitor with a glass look. Needless to say, an amateur.

Hmm, the source code for WakaTime is open: you can try to understand how it works ...
, ? IDE JetBrains.

. WakaTime.java. , , Heartbeat (), WakaTime CLI . , , , .

Writing your bike by analogy


Perhaps a potato gun would be more useful, but you won’t do anything for research purposes. After meditating on the WakaTime sources, I decided to create my own plug-in that can track activity in the IDE, log the time for writing, and send it all to Jira (in our flow, the branches are named after the tasks in Jira, so it looks quite realistic).

And in order not to fill the server with requests while working on the code, we will send the logs when Android Studio is closed, and until that moment store it locally.

1. What entities can we find in the source code of the plugin (and use in our own)?


We do not use Components (this is Legacy ) , which previously were the main structural units of the plugin. May be application level, project level, or module level. There is an ApplicationComponent in the WakaTime source, but they can, the source code is already five years old and it should maintain backward compatibility. Components are tied to the life cycle of the level with which they are associated. So, for example, ApplicationComponent is loaded when the IDE starts. When used, they will block the plugin from restarting without restarting the IDE and generally behave unpleasantly. Therefore, it is better to use services instead.

We use Services- current main structural units. They are divided into the same levels of the application, project and module, they will help us encapsulate the logic at the appropriate levels and store the state of the plugin.

Unlike components, Services must be loaded on its own using the ServiceManager.getService () method. The platform ensures that every service is a singleton.

Add Actions - they can be a shortcut, an additional menu item - in a word, they are responsible for everything that somehow affects user actions in the IDE.

We use Extensions - any extension of functionality that will be more complicated than Actions: for example, get connected to the IDE life cycle and do something while showing a splash screen or before exiting.

All this should be declared in the file /META_INF/plugin.xml.

2. Plugin.xml and build.gradle



Project creation window. We will write our plugin in Kotlin, and use Gradle for the assembly.

This is the first thing we see after creating the plugin blank in IDEA (File -> New -> Project ... -> Gradle -> IntelliJ Platform Plugin). It contains information about the dependencies of the plugin and its brief description. It should declare plug-in components - the previously mentioned components, services, actions and extensions. There will be no specific entry point — it will be a custom action or IDE lifecycle event, depending on our needs.

We will need these dependencies - after all, we want to write a plugin for the studio and so that it can work with Git.

<depends>Git4Idea</depends>
<depends>com.intellij.modules.androidstudio</depends>

Git4Idea is a plug-in for the virtual file system (VCS) and provides topics, thanks to which we can later listen to git events - such as checkouts, for example.

Since we also want the plugin to be able to work with Jira, we will connect through the gradle a kindly provided library with a rest client. To do this, add the Atlassian maven repository there:

repositories {
   mavenCentral()
   maven {
       url "https://packages.atlassian.com/maven/repository/public"
   }
}

And actually the libraries:

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"
   }
}

Here we determine the version of the IDE that interests us and the path to it (oh, if the real Android Studio started and worked as fast as its lightweight version for debugging plugins):

intellij {
   version '2019.1'
   plugins 'git4idea'
   alternativeIdePath 'E:\\Android Studio'
}

And here, perhaps, someday we will write about a new, improved functionality. But not now.

patchPluginXml {
   changeNotes """
     Add change notes here.<br>
     <em>most HTML tags may be used</em>"""
}

3. Creating a UI


To push something into Jira, you first need to log in. It is logical to do this right after the opening of the project. It looks like we need an extension, and we will not hesitate to declare it in plugin.xml:

<postStartupActivity implementation="Heartbeat"/>

and show the dialogue in it.

UI components are mainly components from Swing with some extensions from platform sdk. All this has recently been wrapped in kotlin ui dsl. At the time of writing, the documentation noted that dsl can change very much between major versions, so we use it with a slight fear:


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()
       }
   }
}

We created a dialogue, showed it, received credentials from Jira. Now we need to save them, and preferably safely, as much as possible. PersistingStateComponent will come to the rescue.

4. PersistingStateComponent device (not very complicated)


PersistingStateComponent can do two things - saveState and loadState: serialize and save the object passed to it and retrieve it from storage.

Where exactly he should save the data, he learns from the State annotation. Two simplest options are mentioned in the documentation - specify your file or store everything in the workspace settings:

@Storage("yourName.xml")
@Storage(StoragePathMacros.WORKSPACE_FILE) 
@State(
   name = "JiraSettings",
   storages = [
       Storage("trackerSettings.xml")
   ])

Since we have a special case, we turn to the documentation for the storage of sensitive data.

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)
}

Another PersistingStateComponent will deal with storing the time logged for different branches.

Jira figured out the login. With the storage of user data, too. Now you need to somehow track the checkout event on the branch to start the countdown.

5. Subscribe to events


IntelliJ Platform provides an opportunity to hang Observer on events of interest to us by subscribing to their topics in the messageBus of the desired level. Here's the dock , it's pretty interesting.

A topic is a certain endpoint, a representation of an event. All you need to do is subscribe and implement a listener that should handle what is happening.

For example, I need to listen to checkouts in Git ...

subscribeToProjectTopic(project, GitRepository.GIT_REPO_CHANGE) {
    GitRepositoryChangeListener {
        currentBranch = it.currentBranch
    }
}

... closing the application (to happily pawn time) ...

subscribeToAppTopic(AppLifecycleListener.TOPIC) {
    object: AppLifecycleListener {
        override fun appWillBeClosed(isRestart: Boolean) {
            if (!isRestart) {
                JiraClient.logTime(entry.key, DateTime.now(), entry.value)
            }
        }
    }
}

... and saving documents (just so that it was).

subscribeToAppTopic(AppTopics.FILE_DOCUMENT_SYNC) {
    CustomSaveListener()
}

This, in principle, is already enough (if not, you can always write your own). But what if we want to hear the slightest scroll and mouse movement?

6. EditorEventMulticaster


It collects everything that happens in the open editor windows - editing, changing the visible area, mouse movements - and allows you to subscribe to it in one place and immediately.

Now we have everything we need. We can select the name of the task in Jira from the current branch (if it is there), calculate the time spent working on it, and immediately exit the studio to add it to the task.

7. Code and details here


To run, you need the latest Android Studio (3.6.1).
To test the performance - git and a branch with a name that is at least remotely similar to the task in Jira.
So that the plugin does not just output time to the console, but tracks it - uncomment the corresponding line in Heartbeat.kt.

8. Useful links



PS And what did you choose in the end?


- Damn, it's kind of too evil. And if I do not write code? And if I pick my nose and try hard to understand what is happening in it? In the code, in the sense of. After all, this takes most of the time.

- Then at least scroll it.

- So it’s not far from simulating violent activity. And then, I can not shut down the studio for weeks and not reboot. Maybe we do not need such happiness?

“Definitely not necessary.” Then another reason is needed, otherwise I liked writing plugins. And I still did not really understand VCS.

“Do you use it yourself?”

- Nope. I somehow learned to log with my hands, imperceptibly for myself.

All Articles