// ==UserScript==
// @name pingcodeHelper
// @namespace http://tampermonkey.net/
// @version 3.11
// @description hack pingcode
// @author Amos
// @match https://onetoken.pingcode.com/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant GM_registerMenuCommand
// ==/UserScript==
/* globals jQuery, $, waitForKeyElements */
GM_registerMenuCommand('Archive tasks done before 2 months', function() {
get_work_items().then(items=>{
let longSetTasks=[]
for(let item of items){
if(item.state_type===3||item.state_type===4){
// console.log('checking',(Date.now()-item.updated_at*1000)/24/3600000,item)
if(Date.now()-item.updated_at*1000>24*60*3600*1000){
longSetTasks.push(item)
}
}
}
console.log(longSetTasks)
// let toArchiveItems=longSetTasks.slice(0,2)
// console.log(toArchiveItems)
archiveItems(longSetTasks).then(()=>{
alert(`共归档${longSetTasks.length}个任务`)
}).catch(e=>{
alert(e)
})
})
}, 'r');
function get_work_items(pageSize=1000,pageIndex=0){
return new Promise((resolve,reject)=>{
$.ajax({
url: 'https://onetoken.pingcode.com/api/agile/work-items',
dataType: "json",
data: {ps:pageSize,pi:pageIndex},
async: true,
cache: false,
timeout: 30000,
success: (res)=> {
if(res.code===200){
let data=res.data
let dataList=data.value
if(data.page_index<data.page_count-1){
get_work_items(pageSize,pageIndex+1).then(items=>{
let concatList=dataList.concat(items)
resolve(concatList)
})
} else {
resolve(dataList)
}
} else{
reject()
}
},
error: (request, status, error)=> {
reject(error)
},
type: "GET"
});
})
}
function archiveItems(items,index=0){
return new Promise((resolve,reject)=>{
if(items.length===0){
resolve()
return
}
let item=items[index]
$.ajax({
url: `https://onetoken.pingcode.com/api/agile/work-items/${item._id}/archive`,
dataType: "json",
data: {},
async: true,
cache: false,
timeout: 10000,
success: function (data) {
if(index<items.length-1){
archiveItems(items,index+1).then(res=>{
resolve()
})
} else {
resolve()
}
},
error: function (request, status, error) {
reject(index)
},
type: "PUT"
});
})
}
// $.ajax({
// url: 'https://onetoken.pingcode.com/api/agile/work-items/60e29290793b014b8ffbdeba/archive',
// dataType: "json",
// data: {},
// async: true,
// cache: false,
// timeout: 30000,
// success: function (data) {
// // my success stuff
// },
// error: function (request, status, error) {
// // my error stuff
// },
// type: "PUT"
// });
var pageURLCheckTimer = setInterval(
function () {
if (this.lastPathStr !== location.pathname ||
this.lastQueryStr !== location.search ||
this.lastPathStr === null ||
this.lastQueryStr === null
) {
this.lastPathStr = location.pathname;
this.lastQueryStr = location.search;
gmMain();
}
}, 222
);
function gmMain() {
setTimeout(function () {
let x = document.querySelector("#app-host-container > app-agile-root > app-agile-actual-root > agile-global > agile-global-query-detail > thy-header > div.layout-header-content > div > thy-nav > a.styx-secondary-nav-link.nav-link > span")
if(x){
console.log(x);
console.log(x.textContent.trim());
document.title = x.textContent.trim();
}
}, 3000);
}
function refreshUnread(){
let authorization = localStorage.getItem('authorization')
if(authorization){
fetch('https://iris.pingcode.com/api/iris/notifications/n-unreads?t=1636023609632',{headers:{authorization: authorization}})
.then(res => res.json())
.then(res=>{
if(res.code===200){
let unreadSize=res.data.value
let currentTitle=document.head.querySelector('title').textContent
if(currentTitle.startsWith('(')){
let endIndex = currentTitle.indexOf(')')
if(endIndex>=0){
let newTitle=`(${unreadSize})${currentTitle.substring(endIndex+1)}`
document.head.querySelector('title').textContent=newTitle
}
}
}
})
}
}
function hideYearJump(){
let prevYear=document.querySelector(".thy-calendar-prev-year-btn")
let nextYear=document.querySelector(".thy-calendar-next-year-btn")
if(prevYear){
prevYear.style.display='none'
}
if(nextYear){
nextYear.style.display='none'
}
}
function bestEffortUUID() {
let ts = new Date().getTime()
let hexDigits = '0123456789abcdef'
let uuid = ts + '-'
for (let i = 0; i < 8; i++) {
uuid += hexDigits.substr(Math.floor(Math.random() * 0x10), 1)
}
return uuid
}
(function() {
'use strict';
// let showFinished=true
//let btn=null
let cnt=0
changePriorityWidth()
setInterval(()=>{
shortcutContainerAdjustment()
addHideChildrenLogic()
cnt++
if(cnt%60===0){
refreshUnread()
}
addArchiveChildrenLogic()
hideYearJump()
addCreateChildrenLogic()
},1000)
// type 4 -task
// https://onetoken.pingcode.com/api/agile/work-item POST
// due: {date: 1647964799, with_time: 0}
function createSubTask(parentId,projectId,title,type,due){
let data = {parent_id:parentId,project_id:projectId,title,due,type}
$.ajax({
url: `https://onetoken.pingcode.com/api/agile/work-item`,
dataType: "json",
contentType: "application/json; charset=utf-8",
data:JSON.stringify(data),
async: true,
cache: false,
timeout: 30000,
success: (res)=> {
},
error: (request, status, error)=> {
console.log(error)
},
type: "POST"
});
}
function addCreateBtn(agileDetail,id){
if(agileDetail){
let itemListParent = agileDetail.querySelector('.sub-work-item-list')
if(itemListParent){
console.log('adding btn')
if(agileDetail.querySelector('#createChildrenBtn')===null){
let obj={btn:null,showFinished:true}
obj.btn = document.createElement('div')
obj.btn.textContent='批量创建子任务'
obj.btn.id='createChildrenBtn'
obj.btn.style='color:#aaa;cursor:pointer'
agileDetail.insertBefore(obj.btn, agileDetail.firstChild)
obj.btn.addEventListener('click',()=>{
$.ajax({
url: `https://onetoken.pingcode.com/api/agile/work-items/${id}`,
dataType: "json",
async: true,
cache: false,
timeout: 30000,
success: (res)=> {
if(res.code===200){
let data=res.data
let itemData=data.value
console.log(itemData)
createSubTask(id,itemData.project_id,'产品方案',4,itemData.due)
createSubTask(id,itemData.project_id,'前端开发',4,itemData.due)
createSubTask(id,itemData.project_id,'后端开发',4,itemData.due)
createSubTask(id,itemData.project_id,'测试',4,itemData.due)
createSubTask(id,itemData.project_id,'产品验收',4,itemData.due)
alert('已批量生成子任务,请刷新后查看')
} else{
console.log('code is not 200, but',res.code)
}
},
error: (request, status, error)=> {
console.log(error)
},
type: "GET"
});
})
}
}
}
}
function getAgileDetail(){
let allAgileDetails = document.querySelectorAll('.agile-work-item-detail-work-item')
let agileDetail=null
if(allAgileDetails.length>0){
agileDetail=allAgileDetails[allAgileDetails.length-1]
}
else{
let allAgileDetailChildren = document.querySelectorAll('.agile-work-item-detail-children')
if(allAgileDetailChildren.length>0){
agileDetail=allAgileDetailChildren[allAgileDetailChildren.length-1]
}
}
return agileDetail
}
function addCreateChildrenLogic(){
console.log('start add create children logic')
let agileDetail=getAgileDetail()
if(agileDetail===null||agileDetail.querySelector('#createChildrenBtn')!==null){
console.log('btn is not Null',agileDetail,agileDetail?agileDetail.querySelector('#createChildrenBtn'):null)
return
}
let containers = document.querySelectorAll('.thy-dialog-container')
if(containers.length>0){
let currentContainer = containers[containers.length-1]
let statusEl = currentContainer.querySelector('.thy-label-emboss-status')
if(statusEl){
let taskType=statusEl
.querySelector('.font-size-sm')?.innerHTML
if(taskType!=='用户故事'){
return
}
if(!currentContainer.querySelector('thy-list-item')){
console.log('hasNoChild')
let currentId = currentContainer.id.split('-')[0]
addCreateBtn(agileDetail,currentId)
}
}
}
}
function addArchiveChildrenLogic(){
let agileDetail=getAgileDetail()
if(agileDetail===null||agileDetail.querySelector('#archiveBtn')!==null){
return
}
let containers = document.querySelectorAll('.thy-dialog-container')
if(containers.length>0){
let currentContainer = containers[containers.length-1]
let currentId = currentContainer.id.split('-')[0]
$.ajax({
url: `https://onetoken.pingcode.com/api/agile/work-items/${currentId}`,
dataType: "json",
async: true,
cache: false,
timeout: 30000,
success: (res)=> {
if(res.code===200){
let data=res.data
let itemData=data.value
if(itemData.child_ids.length>=40){
addArchiveBtn(itemData.child_ids,agileDetail,15)
} else if(itemData.child_ids.length>=15){
addArchiveBtn(itemData.child_ids,agileDetail,30)
}
} else{
console.log('code is not 200, but',res.code)
}
},
error: (request, status, error)=> {
console.log(error)
},
type: "GET"
});
}
}
function dateFormat(fmt, date) {
let ret;
const opt = {
"Y+": date.getFullYear().toString(), // 年
"m+": (date.getMonth() + 1).toString(), // 月
"d+": date.getDate().toString(), // 日
"H+": date.getHours().toString(), // 时
"M+": date.getMinutes().toString(), // 分
"S+": date.getSeconds().toString() // 秒
};
for (let k in opt) {
ret = new RegExp("(" + k + ")").exec(fmt);
if (ret) {
fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
};
};
return fmt;
}
function addArchiveBtn(idList,agileDetail,cutoffDays){
if(agileDetail){
let itemListParent = agileDetail.querySelector('.sub-work-item-list')
if(itemListParent){
let itemList = itemListParent.querySelectorAll('.work-items-list-item')
if(itemList.length>0){
console.log('adding btn')
if(agileDetail.querySelector('#archiveBtn')===null){
let obj={btn:null,showFinished:true}
obj.btn = document.createElement('div')
obj.btn.textContent=`归档${cutoffDays}天前的子任务`
obj.btn.id='archiveBtn'
obj.btn.style='color:#aaa;cursor:pointer'
agileDetail.insertBefore(obj.btn, agileDetail.firstChild)
obj.btn.addEventListener('click',()=>{
getItemList(idList).then(workItems=>{
let toArchive = []
let cutoffTime = Date.now()-cutoffDays*24*3600*1000
workItems.forEach(item=>{
let isWorkEnd = item.state_type===3||item.state_type===4
let itemLastUpdated = item.updated_at*1000
if(isWorkEnd&&itemLastUpdated<cutoffTime){
toArchive.push(item)
}
})
if(toArchive.length>0){
showArchiveList(agileDetail,toArchive)
} else {
alert('无可归档任务')
}
})
})
}
}
}
}
}
function showArchiveList(agileDetail,toArchive){
let popupWin = document.querySelector('#pingcode-archive-table')
if(popupWin){
document.body.removeChild(popupWin)
}
popupWin = document.createElement('div')
popupWin.style = "background-color:gray;position: absolute;top: 100px;left: 400px;z-index:10000;height:500px; overflow:auto;padding:10px;"
let titleEl = document.createElement('h4')
titleEl.innerText=`确定归档以下${toArchive.length}个任务?`
popupWin.appendChild(titleEl)
let notifyRoot = document.createElement('div')
popupWin.appendChild(notifyRoot)
notifyRoot.style="height:400px; overflow:auto;"
let table = document.createElement('table')
table.id = 'pingcode-archive-table'
table.style='border:1px'
table.cellspacing="0"
table.cellpadding="1"
table.border="1"
let headerEl = document.createElement('tr')
let idHeadEl = document.createElement('td')
idHeadEl.innerText = '编号'
headerEl.appendChild(idHeadEl)
let titleHeadEl = document.createElement('td')
titleHeadEl.innerText = '标题'
headerEl.appendChild(titleHeadEl)
let stateHeadEl = document.createElement('td')
stateHeadEl.innerText = '状态'
headerEl.appendChild(stateHeadEl)
let lastUpdatedHeadEl = document.createElement('td')
lastUpdatedHeadEl.innerText = '最后更新'
headerEl.appendChild(lastUpdatedHeadEl)
table.appendChild(headerEl)
notifyRoot.appendChild(table)
document.body.appendChild(popupWin)
let btnDiv = document.createElement('div')
btnDiv.style='margin-top:10px;'
popupWin.appendChild(btnDiv)
let confirmBtn = document.createElement('button')
confirmBtn.innerText='确定'
let cancelBtn = document.createElement('button')
cancelBtn.style='margin-left:15px;'
cancelBtn.innerText='取消'
btnDiv.appendChild(confirmBtn)
btnDiv.appendChild(cancelBtn)
cancelBtn.addEventListener('click',()=>{
document.body.removeChild(popupWin)
})
confirmBtn.addEventListener('click',()=>{
archiveItems(toArchive).then(()=>{
alert(`成功归档${toArchive.length}个任务`)
}).catch(size=>{
alert(`成功归档${size}个任务,失败${toArchive.length-size}个`)
}).finally(()=>{
let archiveBtn = agileDetail.querySelector('#archiveBtn')
if(archiveBtn){
agileDetail.removeChild(archiveBtn)
}
})
// console.log(archiveItems)
document.body.removeChild(popupWin)
})
toArchive.forEach(item=>{
let trEl = document.createElement('tr')
table.appendChild(trEl)
let identifierEl = document.createElement('td')
identifierEl.innerText = item.whole_identifier
trEl.appendChild(identifierEl)
let titleEl = document.createElement('td')
titleEl.innerText = item.title
trEl.appendChild(titleEl)
let stateEl = document.createElement('td')
stateEl.innerText = item.state_name
trEl.appendChild(stateEl)
let lastUpdatedEl = document.createElement('td')
lastUpdatedEl.innerText = dateFormat('YYYY-mm-dd HH:MM',new Date(item.updated_at*1000))
trEl.appendChild(lastUpdatedEl)
})
}
function getItemList(idList){
let promises = []
idList.forEach(id=>{
promises.push(getWorkItem(id))
})
return Promise.all(promises)
}
function getWorkItem(id){
return new Promise((resolve,reject)=>{
$.ajax({
url: `https://onetoken.pingcode.com/api/agile/work-items/${id}`,
dataType: "json",
async: true,
cache: false,
timeout: 30000,
success: (res)=> {
if(res.code===200){
let data=res.data
let itemData=data.value
itemData.state_name = data.references.lookups.states[0].name
resolve(itemData)
} else{
reject()
}
},
error: (request, status, error)=> {
reject(error)
},
type: "GET"
});
})
}
function checkIfArchive(id,cutoffDays){
let cutoffTime = Date.now()-cutoffDays*24*3600*1000
$.ajax({
url: `https://onetoken.pingcode.com/api/agile/work-items/${id}`,
dataType: "json",
async: true,
cache: false,
timeout: 30000,
success: (res)=> {
if(res.code===200){
let data=res.data
let itemData=data.value
let isWorkEnd = itemData.state_type===3||itemData.state_type===4
let itemLastUpdated = itemData.updated_at*1000
if(isWorkEnd&&itemLastUpdated<cutoffTime){
$.ajax({
url: `https://onetoken.pingcode.com/api/agile/work-items/${id}/archive`,
dataType: "json",
data: {},
async: true,
cache: false,
timeout: 10000,
success: function (data) {
},
error: function (request, status, error) {
console.log(error)
},
type: "PUT"
});
} else {
console.log('skip',id)
}
} else{
console.log(res.code)
}
},
error: (request, status, error)=> {
console.log(error)
},
type: "GET"
});
}
function addHideChildrenLogic(){
let agileDetail = getAgileDetail()
if(agileDetail){
let itemListParent = agileDetail.querySelector('.sub-work-item-list')
if(itemListParent){
let itemList = itemListParent.querySelectorAll('.work-items-list-item')
if(itemList.length>0){
if(agileDetail.querySelector('#displayBtn')===null){
let obj={btn:null,showFinished:true}
obj.btn = document.createElement('span')
obj.btn.textContent=obj.showFinished?'隐藏已完成':'显示已完成'
obj.btn.id='displayBtn'
obj.btn.style='color:#aaa;cursor:pointer'
agileDetail.insertBefore(obj.btn, agileDetail.firstChild)
obj.btn.addEventListener('click',()=>{
toggleShowFinished(agileDetail,obj)
})
}
}
}
}
}
function changePriorityWidth(){
var sheet = document.createElement('style')
sheet.innerHTML = ".agile .agile-work-items-list-item-container .work-item-priority {width: 8px;}";
document.body.appendChild(sheet);
}
function isEnd(workItem){
let status=workItem.querySelector('.flexible-text-container').textContent
return status==='关闭'||status==='已完成'
}
function shortcutContainerAdjustment(){
let shortcutContainer=document.querySelector('.shortcut-container')
if(shortcutContainer){
let tableContent = document.querySelector('.styx-table-content')
if(tableContent){
if(shortcutContainer.offsetHeight>0){
tableContent.style='margin-bottom:28px'
}
else{
tableContent.style='margin-bottom:0px'
}
}
}
}
function toggleShowFinished(agileDetail,obj){
obj.showFinished=!obj.showFinished
obj.btn.textContent=obj.showFinished?'隐藏已完成':'显示已完成'
if(obj.showFinished){
let workItems=agileDetail.querySelectorAll('.work-item-info')
for(let item of workItems){
item.parentElement.parentElement.style='display:block'
}
} else {
let workItems=agileDetail.querySelectorAll('.work-item-info')
for(let item of workItems){
let status=item.querySelector('.flexible-text-container').textContent
if(status==='关闭'||status==='已完成'||status==='已修复'||status==='已拒绝'){
item.parentElement.parentElement.style='display:none'
}
}
}
}
// Your code here...
})();