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:
- Track hours in a JS file.
- 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 andABC-${number}
if your Jira ticket IDs start withABC-
. - I'm using
null
instead ofFOO-${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.
-
Open the browser's DevTools.
-
Log hours manually in Jira (only one logging needed).
-
Select the Network tab of DevTools and look for the XHR request that was just made.
-
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', } )
-
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
-
Open Jira in the browser.
-
Copy-paste the code (including the
items
object) to the browser's JS console. -
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*).)
-
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!