pingcodeHelper

hack pingcode

À partir de 2022-03-22. Voir la dernière version.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==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...
    
})();