1st version
This commit is contained in:
240
jiracula.user.js
Normal file
240
jiracula.user.js
Normal file
@@ -0,0 +1,240 @@
|
||||
// ==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)
|
||||
|
||||
*/
|
||||
Reference in New Issue
Block a user