pingcodeHelper

hack pingcode

目前為 2022-03-22 提交的版本,檢視 最新版本

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址