-
-
Save herpiko/b5aa126e29f5f2ad15c4ff481bf5aee8 to your computer and use it in GitHub Desktop.
gitlab project time tracking
This file contains 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
const axios = require('axios'); | |
const fs = require('fs'); | |
const conf = { | |
host: 'https://somedomain.com', | |
token: 'TOKEN', | |
projectId: '2', | |
groupId: '1', | |
perPage: 100, | |
maxPage: 5, | |
}; | |
var closedIssuesWithMR = []; | |
const getIssues = (page) => { | |
let url = | |
conf.host + | |
`/api/v4/projects/` + | |
conf.projectId + | |
`/issues?per_page=` + | |
conf.perPage + | |
`&page=` + | |
page + | |
`&order_by=updated_at&state=closed`; | |
return axios | |
.get(url, { headers: { 'PRIVATE-TOKEN': conf.token } }) | |
.then((response) => response.data); | |
}; | |
const getNotes = (url) => { | |
return axios | |
.get(url, { headers: { 'PRIVATE-TOKEN': conf.token } }) | |
.then((response) => response.data); | |
} | |
const getIssueTimeStats = (id) => { | |
let url = | |
conf.host + | |
`/api/v4/projects/` + | |
conf.projectId + | |
`/issues/` + | |
id + | |
`/time_stats`; | |
return axios | |
.get(url, { headers: { 'PRIVATE-TOKEN': conf.token } }) | |
.then((response) => response.data); | |
}; | |
const getMergeRequests = (id) => { | |
let url = | |
conf.host + | |
`/api/v4/groups/` + | |
conf.groupId + | |
`/merge_requests?search=` + | |
id + | |
`&in=title&order_by=updated_at`; | |
return axios | |
.get(url, { headers: { 'PRIVATE-TOKEN': conf.token } }) | |
.then((response) => response.data); | |
}; | |
const processPage = async (page) => { | |
console.log('Fetching issues at page ' + page); | |
let issues = await getIssues(page); | |
for (let i in issues) { | |
console.log( | |
'Processing ' + i + ' of ' + issues.length + ' at page ' + page | |
); | |
let obj = { | |
id: issues[i].iid, | |
created_at: issues[i].created_at, | |
updated_at: issues[i].updated_at, | |
closed_at: issues[i].closed_at, | |
}; | |
console.log('Fetching merge requests of ' + issues[i].iid); | |
let mergeRequests = await getMergeRequests(issues[i].iid); | |
if (mergeRequests.length > 0) { | |
obj.merged_at = mergeRequests[0].merged_at; | |
obj.merge_request_at = mergeRequests[0].created_at; | |
} | |
console.log('Fetching notes of ' + issues[i].iid); | |
let notes = await getNotes(issues[i]._links.notes); | |
for (let j in notes) { | |
if (notes[j].body && notes[j].body.indexOf('assigned to') > -1) { | |
obj.assigned_at = notes[j].created_at; | |
} | |
} | |
if (obj.assigned_at && obj.merged_at) { | |
// Collect only the complete ones | |
closedIssuesWithMR.push(obj); | |
} | |
} | |
}; | |
const analyze = async (items) => { | |
var items = JSON.parse(fs.readFileSync('data-bak.json', 'utf8')); | |
// Remove issues those take too long (more than two weeks) | |
// This maybe a documentation or referenced issue | |
var filtered = []; | |
for (let i in items) { | |
let doing = Math.floor( | |
Math.abs( | |
new Date(items[i].merge_request_at) - new Date(items[i].assigned_at) | |
) / | |
1000 / | |
60 / | |
60 | |
); | |
let test = Math.floor( | |
Math.abs(new Date(items[i].closed_at) - new Date(items[i].merged_at)) / | |
1000 / | |
60 / | |
60 | |
); | |
if (doing < 336 && test < 336) filtered.push(items[i]); | |
} | |
items = filtered; | |
var created_to_closed = []; | |
var created_to_assigned = []; | |
var assigned_to_merge_request = []; | |
var merge_request_to_merged = []; | |
var merged_to_closed = []; | |
for (let i in items) { | |
items[i].created_to_closed = Math.floor( | |
Math.abs(new Date(items[i].closed_at) - new Date(items[i].created_at)) / | |
1000 / | |
60 / | |
60 | |
); | |
created_to_closed.push(items[i].created_to_closed); | |
items[i].created_to_assigned = Math.floor( | |
Math.abs(new Date(items[i].assigned_at) - new Date(items[i].created_at)) / | |
1000 / | |
60 / | |
60 | |
); | |
created_to_assigned.push(items[i].created_to_assigned); | |
items[i].assigned_to_merge_request = Math.floor( | |
Math.abs( | |
new Date(items[i].merge_request_at) - new Date(items[i].assigned_at) | |
) / | |
1000 / | |
60 / | |
60 | |
); | |
assigned_to_merge_request.push(items[i].assigned_to_merge_request); | |
items[i].merge_request_to_merged = Math.floor( | |
Math.abs( | |
new Date(items[i].merged_at) - new Date(items[i].merge_request_at) | |
) / | |
1000 / | |
60 / | |
60 | |
); | |
merge_request_to_merged.push(items[i].merge_request_to_merged); | |
items[i].merged_to_closed = Math.floor( | |
Math.abs(new Date(items[i].closed_at) - new Date(items[i].merged_at)) / | |
1000 / | |
60 / | |
60 | |
); | |
merged_to_closed.push(items[i].merged_to_closed); | |
} | |
// Please fast check | |
// console.log(items[0]) | |
// console.log(items[1]) | |
// console.log(items[2]) | |
// console.log(items[3]) | |
// console.log(items[4]) | |
let reports = { | |
median: {}, | |
mean: {}, | |
}; | |
// Sort | |
created_to_closed.sort((a, b) => { | |
return a - b; | |
}); | |
created_to_assigned.sort((a, b) => { | |
return a - b; | |
}); | |
assigned_to_merge_request.sort((a, b) => { | |
return a - b; | |
}); | |
merge_request_to_merged.sort((a, b) => { | |
return a - b; | |
}); | |
merged_to_closed.sort((a, b) => { | |
return a - b; | |
}); | |
// Median | |
reports.median.created_to_closed = parseInt( | |
created_to_closed[Math.floor(created_to_closed.length / 2)] | |
); | |
reports.median.created_to_assigned = parseInt( | |
created_to_assigned[Math.floor(created_to_assigned.length / 2)] | |
); | |
reports.median.assigned_to_merge_request = parseInt( | |
assigned_to_merge_request[Math.floor(assigned_to_merge_request.length / 2)] | |
); | |
reports.median.merge_request_to_merged = parseInt( | |
merge_request_to_merged[Math.floor(merge_request_to_merged.length / 2)] | |
); | |
reports.median.merged_to_closed = parseInt( | |
merged_to_closed[Math.floor(merged_to_closed.length / 2)] | |
); | |
// Mean | |
function add(accumulator, a) { | |
return accumulator + a; | |
} | |
reports.mean.created_to_closed = parseInt( | |
created_to_closed.reduce(add, 0) / created_to_closed.length | |
); | |
reports.mean.created_to_assigned = parseInt( | |
created_to_assigned.reduce(add, 0) / created_to_assigned.length | |
); | |
reports.mean.assigned_to_merge_request = parseInt( | |
assigned_to_merge_request.reduce(add, 0) / assigned_to_merge_request.length | |
); | |
reports.mean.merge_request_to_merged = parseInt( | |
merge_request_to_merged.reduce(add, 0) / merge_request_to_merged.length | |
); | |
reports.mean.merged_to_closed = parseInt( | |
merged_to_closed.reduce(add, 0) / merged_to_closed.length | |
); | |
console.log(reports); | |
}; | |
const main = async () => { | |
for (let i = 1; i <= conf.maxPage; i++) { | |
await processPage(i); | |
} | |
await analyze(closedIssuesWithMR); | |
}; | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example output (in hours):