240 lines
8.3 KiB
JavaScript
240 lines
8.3 KiB
JavaScript
// ==UserScript==
|
|
// @name Jiracula
|
|
// @namespace https://www.nicsys.eu/
|
|
// @description Bite Jira in the neck and inject your timesheets !
|
|
// @author Nike
|
|
// @match https://citnet.tech.ec.europa.eu/CITnet/jira/secure/Tempo.jspa*
|
|
// @grant none
|
|
// @version 1.0
|
|
// @grant GM_notification
|
|
// ==/UserScript==
|
|
// ___
|
|
// | |
|
|
// | |__ __
|
|
// | |__|___________ ____ __ __| | _____
|
|
// | | \_ __ \__ \ _/ ___\| | \ | \__ \
|
|
// /\__| | || | \// __ \\ \___| | / |__/ __ \_
|
|
// \_____ |__||__| (____ /\_____>____/|____(______/
|
|
// \/ \/
|
|
// By Nike
|
|
|
|
|
|
(function() {
|
|
'use strict'
|
|
|
|
function addBtn(){
|
|
const ul = document.querySelector('div.aui-header-primary ul.aui-nav')
|
|
const style=`
|
|
background: linear-gradient(to bottom, #b30000, #4d0000);
|
|
color: white !important;
|
|
text-shadow: 0 0 2px #ff0000, 0 0 5px #800000;
|
|
border: 1px solid #330000;
|
|
`
|
|
ul.insertAdjacentHTML('beforeend',`<li><a class="aui-button aui-button-primary aui-style" title="Jiracula" style="${style}">Jiracula</a></li>`)
|
|
const btn = ul.lastElementChild.querySelector('a')
|
|
btn.addEventListener('click', biteJira)
|
|
}
|
|
|
|
function alert(type, msg){
|
|
window.postMessage({
|
|
from: 'jiracula',
|
|
type: type,
|
|
message: msg
|
|
}, '*')
|
|
}
|
|
|
|
function biteJira(){
|
|
fileSelector(async content => {
|
|
let tasksData = null
|
|
try{
|
|
tasksData = JSON.parse(content)
|
|
if(!Array.isArray(taskData)) throw new Error('Not an array!')
|
|
} catch(err){
|
|
alert('Error', 'Could not parse the file !\n(Should be JSON array)')
|
|
}
|
|
if(tasksData){
|
|
const euid = document.getElementById('header-details-user-fullname').dataset.username
|
|
const userKey = await getUserKey(euid)
|
|
|
|
const modal = showProgressModal("Encoding worklogs…")
|
|
const percInc = 100/tasksData.length
|
|
let perct = 0
|
|
for(const row of tasksData){
|
|
console.log('===Injecting==>', row)
|
|
const taskId = await getTaskId(row.TaskCode)
|
|
console.log('=taskID==>', taskId)
|
|
await postWorklog({
|
|
"attributes": {},
|
|
"billableSeconds": "",
|
|
"worker": userKey,
|
|
"comment": row.Description,
|
|
"started": toIsoDate(row.Date),
|
|
"timeSpentSeconds": 3600*row.Duration,
|
|
"originTaskId": taskId,
|
|
"remainingEstimate": null,
|
|
"endDate": null,
|
|
"includeNonWorkingDays": false
|
|
})
|
|
|
|
perct += percInc
|
|
modal.update(perct)
|
|
}
|
|
|
|
modal.close()
|
|
//forceReload()
|
|
}
|
|
})
|
|
}
|
|
|
|
function forceReload() {
|
|
const url = new URL(location.href)
|
|
url.searchParams.set('_cb', Math.random().toString(36).slice(2))
|
|
location.replace(url.toString())
|
|
}
|
|
|
|
function fileSelector(contentReady){
|
|
const input = document.createElement('input')
|
|
input.type = 'file'
|
|
input.accept = '.jrcl.json' // optional filter
|
|
input.style.display = 'none'
|
|
document.body.appendChild(input)
|
|
|
|
input.addEventListener('change', () => {
|
|
const file = input.files[0]
|
|
const reader = new FileReader()
|
|
reader.onload = e => { contentReady(e.target.result) }
|
|
reader.readAsText(file)
|
|
})
|
|
input.click()
|
|
}
|
|
async function getUserKey(EULogin){
|
|
const url = `https://citnet.tech.ec.europa.eu/CITnet/jira/rest/api/2/user?username=${EULogin}`
|
|
const res = await fetch(url, {
|
|
method: "GET",
|
|
credentials: "same-origin", // 🔑 ensures JSESSIONID + XSRF cookies are sent
|
|
headers: {
|
|
"Accept": "application/json",
|
|
"Content-Type": "application/json",
|
|
"Referer": "https://citnet.tech.ec.europa.eu/CITnet/jira/secure/Tempo.jspa"
|
|
}
|
|
})
|
|
const resObject = await res.json()
|
|
return(resObject.key)
|
|
}
|
|
|
|
async function getTaskId(code){
|
|
const url = `https://citnet.tech.ec.europa.eu/CITnet/jira/rest/api/latest/issue/${code}`
|
|
const res = await fetch(url, {
|
|
method: "GET",
|
|
credentials: "same-origin", // 🔑 ensures JSESSIONID + XSRF cookies are sent
|
|
headers: {
|
|
"Accept": "application/json",
|
|
"Content-Type": "application/json",
|
|
"Referer": "https://citnet.tech.ec.europa.eu/CITnet/jira/secure/Tempo.jspa"
|
|
}
|
|
})
|
|
const resObject = await res.json()
|
|
return(resObject.id)
|
|
}
|
|
|
|
async function postWorklog(data) {
|
|
const url = "https://citnet.tech.ec.europa.eu/CITnet/jira/rest/tempo-timesheets/4/worklogs/"
|
|
const res = await fetch(url, {
|
|
method: "POST",
|
|
credentials: "same-origin", // 🔑 ensures JSESSIONID + XSRF cookies are sent
|
|
headers: {
|
|
"Accept": "application/json",
|
|
"Content-Type": "application/json",
|
|
"Referer": "https://citnet.tech.ec.europa.eu/CITnet/jira/secure/Tempo.jspa"
|
|
},
|
|
body: JSON.stringify(data)
|
|
})
|
|
|
|
if (!res.ok) {
|
|
throw new Error(`Jiracula failed: ${res.status} ${res.statusText}`)
|
|
}
|
|
|
|
return await res.json()
|
|
}
|
|
|
|
function toIsoDate(dmy) {
|
|
const [d, m, y] = dmy.split('/')
|
|
const fullYear = 2000 + Number(y) // adjust century rule if needed
|
|
return `${fullYear}-${m.padStart(2, '0')}-${d.padStart(2, '0')}T00:00:00.000`
|
|
}
|
|
|
|
function showProgressModal(title = "Working…") {
|
|
const style = document.createElement('style')
|
|
style.textContent = `
|
|
#jiracula-modal {
|
|
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
|
background: rgba(0,0,0,0.6);
|
|
display: flex; align-items: center; justify-content: center;
|
|
z-index: 999999;
|
|
}
|
|
#jiracula-box {
|
|
background: #fff; padding: 20px; border-radius: 8px;
|
|
box-shadow: 0 0 20px rgba(0,0,0,0.5);
|
|
min-width: 300px; text-align: center;
|
|
font-family: sans-serif;
|
|
}
|
|
#jiracula-box h2 { margin: 0 0 10px; font-size: 18px; }
|
|
#jiracula-box progress { width: 100%; }
|
|
`
|
|
document.head.appendChild(style)
|
|
const modal = document.createElement('div')
|
|
modal.id = 'jiracula-modal'
|
|
modal.innerHTML = `
|
|
<div id="jiracula-box">
|
|
<h2>${title}</h2>
|
|
<progress id="jiracula-progress" value="0" max="100"></progress>
|
|
</div>
|
|
`
|
|
document.body.appendChild(modal)
|
|
return {
|
|
update(val) {
|
|
document.getElementById('jiracula-progress').value = val
|
|
},
|
|
close() {
|
|
modal.remove()
|
|
style.remove()
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
addBtn()
|
|
window.addEventListener('message', e => { // Listen for page→sandbox messages
|
|
if (e.source !== window) return // only our own page
|
|
if (!e.data || e.data.from !== 'jiracula') return
|
|
|
|
if (e.data.type === 'error') {
|
|
console.error('Jiracula error:', e.data.message)
|
|
if (typeof GM_notification === 'function') {
|
|
GM_notification({
|
|
title: 'Jiracula Error',
|
|
text: e.data.message,
|
|
timeout: 5000
|
|
})
|
|
} else {
|
|
alert('Jiracula Error: ' + e.data.message)
|
|
}
|
|
}
|
|
})
|
|
})()
|
|
|
|
|
|
/**
|
|
const modal = showProgressModal("Uploading worklogs…")
|
|
|
|
let progress = 0
|
|
const interval = setInterval(() => {
|
|
progress += 10
|
|
modal.update(progress)
|
|
if (progress >= 100) {
|
|
clearInterval(interval)
|
|
modal.close()
|
|
}
|
|
}, 500)
|
|
|
|
*/ |