Created
August 8, 2020 16:14
-
-
Save bhouston/f83f099fe2996a84e9584ee90534dfdb to your computer and use it in GitHub Desktop.
parse gitlab time spends from notes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Spend } from '../plan/model.mjs'; | |
import nodemon from 'nodemon'; | |
import { log, logPush, logPop } from '../logging.mjs'; | |
var spendBodyRegex = /(?<operation>(added|subtracted)) (?<duration>([0-9]+[mowdhs]+ )*)(of time spent at )(?<date>[0-9\-]+)/; | |
var spendResetBody = "removed time spent"; | |
//var durationRegex = new RegExp( '^((?<months>[0-9]+)mo(\\w)*)?((?<weeks>[0-9]+)w(\\w)*)?((?<days>[0-9]+)d(\\w)*)?((?<hours>[0-9]+)h(\\w)*)?((?<minutes>[0-9]+)m(\\w)*)?((?<seconds>[0-9]+)s(\\w)*)?' ); | |
const durationRegex = /((?<months>[0-9]+)mo(\w)*)?((?<weeks>[0-9]+)w(\w)*)?((?<days>[0-9]+)d(\w)*)?((?<hours>[0-9]+)h(\w)*)?((?<minutes>[0-9]+)m(\w)*)?((?<seconds>[0-9]+)s(\w)*)?/gm; | |
//var durationRegex = /((((?<mo>[0-9]+)mo)|((?<w>[0-9]+)w)|((?<d>[0-9]+)d)|((?<h>[0-9]+)h)|((?<m>[0-9]+)m)|((?<s>[0-9]+)s))( )*)+/g; | |
function parseIntIfExists( result ) { | |
if( result !== undefined ) { | |
return parseInt( result ); | |
} | |
return 0; | |
} | |
function parseDurationToSeconds( duration ) { | |
var totalDurationInSeconds = 0; | |
var groups = {}; | |
let result; | |
while ( result = durationRegex.exec(duration) ) { | |
//console.log( 'result.groups', result.groups ); | |
// This is necessary to avoid infinite loops with zero-width matches | |
if (result.index === durationRegex.lastIndex) { | |
durationRegex.lastIndex++; | |
} | |
//console.log( result ); | |
// roll up based on custom GitLab unit usage: | |
// https://docs.gitlab.com/ee/user/project/time_tracking.html#configuration | |
var durationInMonths = parseIntIfExists( result.groups.months ); | |
var durationInWeeks = parseIntIfExists( result.groups.weeks ) + 4 * durationInMonths; | |
var durationInDays = parseIntIfExists( result.groups.days ) + 5 * durationInWeeks; | |
var durationInHours = parseIntIfExists( result.groups.hours ) + 8 * durationInDays; | |
var durationInMinutes = parseIntIfExists( result.groups.minutes ) + 60 * durationInHours; | |
var durationInSeconds = parseIntIfExists( result.groups.seconds ) + 60 * durationInMinutes; | |
totalDurationInSeconds += durationInSeconds; | |
//console.log( 'totalDurationInSeconds', totalDurationInSeconds ); | |
} | |
return totalDurationInSeconds; | |
} | |
function isSpendAutomatic( username, spendDate ) { | |
if( username === 'threekit_auth' ) return true; | |
if( username === 'bhouston' && spendDate.getTime() < new Date( 2020, 0, 1 )) return true; | |
return false; | |
} | |
function processNoteSpends( parent, notes, newSpendCallback ) { | |
// TODO: support reset of spends. | |
var reversedNotes = []; | |
if( notes ) { | |
notes.forEach( note => { | |
reversedNotes.unshift( note ); | |
}); | |
} | |
var totalDuration = 0; | |
reversedNotes.forEach( note => { | |
if( note.system ) { | |
var result = spendBodyRegex.exec( note.body ); | |
if( result ) { | |
var scale = ( result.groups.operation === 'added' ) ? 1 : -1; | |
var duration = parseDurationToSeconds( result.groups.duration ) * scale; | |
var date = new Date( result.groups.date ); | |
if( ! isSpendAutomatic( note.author.username, date ) ) { | |
newSpendCallback( note.author.username, date, duration, 'system' ); | |
} | |
totalDuration += duration; | |
} | |
else if( note.body === spendResetBody ) { | |
var duration = ( - totalDuration ); | |
var date = new Date( note.created_at ); | |
if( isSpendAutomatic( note.author.username, date ) ) { | |
newSpendCallback( note.author.username, date, duration, 'system' ); | |
} | |
totalDuration += duration; | |
} | |
} | |
else if( note.internal ) { | |
var duration = note.duration; | |
var date = new Date( note.created_at ); | |
if( isSpendAutomatic( note.username, date ) ){ | |
newSpendCallback( note.username, date, note.duration, 'internal' ); | |
} | |
totalDuration += duration; | |
} | |
}); | |
// if there is left over time, it was because the spend was done in the description of the issue. | |
if( parent.original_time_stats.total_time_spent > totalDuration ) { | |
if( parent.labels['type'] !== 'epic' ) { // this can not be used on an epic, because we set the time on it. | |
var duration = parent.original_time_stats.total_time_spent - totalDuration; | |
newSpendCallback( parent.author.username, new Date( parent.created_at ), duration, 'remainder' ); | |
} | |
} | |
} | |
export function recordNoteSpends( plan, task, parent, notes ) { | |
return processNoteSpends( parent, notes, ( username, date, duration, spendType ) => { | |
let spend = plan.requestSpend( username, date, task, duration ); | |
if( (!spend.existing) && parent && parent.iid === 2598 ) { | |
spend.existing = true; | |
log( `processNoteSpends( epic ${parent.iid} task ${task.iid} date ${date} duration ${duration} type ${spendType} )` ); | |
log( JSON.stringify( notes ) ); | |
} | |
}); | |
} | |
export function remapNoteSpends( parent, notes, issues ) { | |
return processNoteSpends( parent, notes, ( username, date, duration ) => { | |
issues.forEach( issue => { | |
issue.notes.push( { | |
'internal': true, | |
'username': username, | |
'created_at': date.toISOString(), | |
'duration': Math.round( duration / issues.length ) | |
}); | |
}) | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment