Programming ≈ Fun

Written by Krešimir Bojčić

JIRA Worklog From Command Line

If you are using JIRA to log your working hours I think the best policy is to write it right after you are done with particular task. In the process I’ve experienced a couple of non-happy-path-scenarios such as:

  1. JIRA is down
  2. JIRA is slow (I am complicated about speed, kinda like Corey Haines)

In order to mitigate that, I’ve started writing notes to .txt file. I would then type-in all hours at the end of the day. It’s just me and my beloved Vim. What could go wrong? Right?

Well, it sounded better in theory as it had its own share of problems, such as:

  1. Keeping the file open all day long (and more so finding it)
  2. Forcing myself to fiddle with JIRA for x tasks (not to mention “at the end of the day” part)

Command Line To The Rescue

At the end I’ve settled with using command line for logging work to intermediate “send” file (instantaneous) and than at the end of the day sending the whole batch to JIRA via API.

rake log:work[1,1h,'Really ground breaking work']
rake log:work[1,2h,'Best thing since sliced bread']
rake log:work[1,3h,'Finding missing semicolon']
rake log:work[OTHERPROJ-1,2h,'Figuring out how to send parms via Rake :)']

rake log:send
Rake is a bit moody as interface. You need to watch out not to use comma except as a token delimiter. (For example I didn’t find the way to use it in comment). And it’s super sensitive to spaces so I tend not to write them.

My language of choice was Ruby, and for interface I’ve used Rake. Mostly because that way I can add recurring tasks to rake (such as holidays…) and I can list all my tasks for free with:

rake -D

The code is not all that important as I had much more trouble finding the optimal workflow. Nevertheless I’ll put the code below in hope that someone will find it useful:

(log_work.rb) download
require 'yaml'

class LogWork
  class << self
    SendList = "send_list.yaml"

    def add_work(task_key, time_spent, comment, go_back_in_days = 0)
      list = send_list
      list <<  [task_key, time_spent, comment, go_back_in_days]
      save(list)
    end

    def send_all(target)
      sent, list = [], send_list
      list.each { |task| sent << task if target.add_work(task[0], task[1], task[2], task[3]) }
      save(list - sent)
    end

    def send_list() File.exists?(SendList) ? File.open(SendList, "r") { |file| YAML.load(file) } : [] end
    def save(list) File.open(SendList, "w") {|file| file.puts(list.to_yaml) } end
    def reset() File.delete(SendList) if File.exists?(SendList) end
  end
end
(Rakefile) download
require 'rake'
require_relative 'log_work'
require_relative 'jira'

namespace :log do
  desc 'Add worklog to JIRA'
  task :work, [:task_key, :time_spent, :comment, :go_back_in_days] do |t, args|
    args.with_defaults(:go_back_in_days => 0)
    task_key = args[:task_key]
    task_key = task_key.to_i if task_key =~ /^[0-9]+$/
    LogWork.add_work(task_key, args[:time_spent], args[:comment], args[:go_back_in_days].to_i)
  end

  desc 'Send all worklogs to JIRA'
  task :send do
    LogWork.send_all(Jira)
  end
end

Talking to JIRA is done via Savon:

(jira.rb) download
require 'jiraSOAP'
require 'savon'

Savon.configure do |config|
  config.log = false
  config.log_level = :info
end

class Jira

  ServiceURI = #you_jira_address
  ServiceWSDL = "#{ServiceURI}/rpc/soap/jirasoapservice-v2?wsdl"
  UserName = #your_user_name
  Password = #your_password
  DefaultProject = #your_default_project

  def self.add_work(task_key, time_spent, comment, go_back_in_days = 0)
     task_key = "#{DefaultProject}-#{task_key}" unless task_key.to_s.upcase =~ /^[A-Z]+-[0-9]+$/
     go_back_in_days = go_back_in_days.to_i
     result = false
     begin
       db = JIRA::JIRAService.new ServiceURI
       db.login UserName, Password
       client = Savon::Client.new ServiceWSDL
       response = client.request 'addWorklogAndRetainRemainingEstimate'  do
         soap.body = {
           :in0 => db.auth_token,
           :in1 => task_key,
           :in2 =>  { :timeSpent => time_spent, :comment => comment , :startDate => DateTime.now - go_back_in_days }
         }
       end
       result = true
     rescue
       print $!
       result = false
     end
     return result
   end

end

Comments