Logging hours to Jira's "Work log" in bulk

Published on in JavaScript

There's apparently no nice way to bulk-log hours in Jira. Use this One Weird Trick to do it anyway – Jira consultants HATE it!

Table of contents

Problem

Jira's UI for logging hours ("Work log" / "Time tracking") is clunky:

  • It requires lots of mouse clicks and/or key presses.
  • It gets very repetitive after a few times.
  • The UI keeps changing from time to time, breaking my workflow.

Officially hours can apparently be logged in bulk by importing data from a CSV file, but that sounds clunky and doesn't work if your user account doesn't have permission to import data from CSV files.

Solution

Overview:

  1. Track hours in a JS file.
  2. Log the hours by copy-pasting the code and running it in the browser's JS console.

Tracking hours

Create a JavaScript file and keep track of hours with this fancy format:

// @ts-check

/**
 * @type {Record<
 *   `${'Mon' | 'Tue' | 'Wed' | 'Thu' | 'Fri'} 2023-11-${number}`,
 *   [ticketId: `FOO-${number}` | null, hours: number][]
 * >}
 */
const items = {
  // ...
  'Wed 2023-11-08': [
    ['FOO-123', 1.5], // pr fixes
    ['FOO-333', 5], // feature x
    ['FOO-444', 1], // pr review
  ],
  'Tue 2023-11-07': [
    ['FOO-222', 3], // yarn install investigation
    [null, 1], // backlog grooming
    ['FOO-333', 3.5], // feature x
  ],
  'Mon 2023-11-06': [
    ['FOO-123', 7], // nav revamp
    [null, 0.5], // daily
  ],
}

Notes:

  • Use JS instead of TS so that you can later copy-paste the code directly to the browser's JS console.
  • The // @ts-check comment at the top lets you still get the benefits of TS; it makes VS Code nag at you if you get some values wrong.
  • Adjust the date and ticket ID types accordingly; e.g. 2023-12-${number} when it's December and ABC-${number} if your Jira ticket IDs start with ABC-.
  • I'm using null instead of FOO-${number} for general stuff because I can't remember the "general stuff" ticket's ID.
  • The code comments are for you so you'll remember later how you have been spending your time.

Logging hours

Initial setup

This needs to be done only once.

  1. Open the browser's DevTools.

  2. Log hours manually in Jira (only one logging needed).

  3. Select the Network tab of DevTools and look for the XHR request that was just made.

  4. Right-click on the item and select "Copy as Fetch." The code might look something like this:

    await fetch(
      'https://jira.example.com/rest/internal/3/issue/FOO-123/worklog?adjustEstimate=new&newEstimate=0m',
      {
        credentials: 'include',
        headers: {
          'User-Agent': 'redacted for privacy lolz',
          Accept: 'application/json,text/javascript,*/*',
          'Accept-Language': 'en-US,en;q=0.5',
          'Content-Type': 'application/json',
          'X-Atlassian-Capability': 'ISSUE_VIEW',
          'Sec-Fetch-Dest': 'empty',
          'Sec-Fetch-Mode': 'cors',
          'Sec-Fetch-Site': 'same-origin',
          Pragma: 'no-cache',
          'Cache-Control': 'no-cache',
        },
        referrer: 'https://jira.example.com/browse/FOO-123',
        body:
          '{"timeSpent":"30m","comment":{"type":"doc","version":1,"content":[]},"started":"2023-10-30T08:00:00.000+0200"}',
        method: 'POST',
        mode: 'cors',
      }
    )
    
  5. Add the following snippet to the JS file after the items object; notice how I have removed unnecessary Fetch options and replaced hardcoded values with template strings:

    const items = {
      // ...
    }
    
    ;(async () => {
      for (let [date, rows] of Object.entries(items)) {
        date = date.split(' ')[1] // E.g. "Mon 2023-10-23" -> "2023-10-23"
    
        for (let [ticketId, time] of rows) {
          ticketId ??= 'FOO-111' // "General stuff" ticket
          time *= 60 // Hours to minutes
    
          await fetch(
            `https://jira.example.com/rest/internal/3/issue/${ticketId}/worklog?adjustEstimate=new&newEstimate=0m`,
            {
              body: JSON.stringify({
                timeSpent: `${time}m`,
                comment: { type: 'doc', version: 1, content: [] },
                started: `${date}T08:00:00.000+0200`,
              }),
              headers: {
                Accept: 'application/json',
                'Content-Type': 'application/json',
              },
              method: 'POST',
              referrer: `https://jira.example.com/browse/${ticketId}`,
            }
          )
        }
      }
    })()
    

    There might still be some fluff (I don't know if e.g. the referrer is needed), but good enough.

Actually logging

  1. Open Jira in the browser.

  2. Copy-paste the code (including the items object) to the browser's JS console.

  3. Press Enter in the JS console and switch to the Network tab to monitor whether the Fetch requests succeed or fail. (So far they have never failed for me (*knocks on wood*).)

  4. Comment out the logged items in the JS file to avoid accidentally re-logging them the next time:

    const items = {
      // 'Mon 2023-11-06': [
      //   ...
      // ],
    }
    

    (Or just remove the logged items.)

Nice and easy!