// ==UserScript==
// @name Van Deploy
// @namespace http://tampermonkey.net/
// @version 0.5.4
// @description Auto Deploy for Van!
// @author Alexander
// @match https://van.huolala.work/projects/835/*
// @match https://van.huolala.work/projects/833/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant none
// @license MIT
// ==/UserScript==
const getStorage = (name) => {
// const cookieObj = document.cookie.split(';').reduce((acc, cur) => {
// const [key, value] = cur.split('=');
// acc[key.trim()] = value;
// return acc;
// } , {})
// return cookieObj[name || ''] || '';
return localStorage.getItem(name || '') || '';
};
const setStorage = (name, value) => {
// const cookieObj = document.cookie.split(';').reduce((acc, cur) => {
// const [key, value] = cur.split('=');
// acc[key.trim()] = value;
// return acc;
// } , {})
// cookieObj[name] = value;
// const newCookie = Object.keys(cookieObj).map(i => `${i}=${cookieObj[i]}`).join(';');
// document.cookie = newCookie;
localStorage.setItem(name, value);
};
(function() {
'use strict';
// console.log(' van deploy is running ')
let monitor;
const startTime = new Date().getTime();
const sleep = (time = 1000) => new Promise(resolve => setTimeout(resolve, time));
const committerDeployMap = {
'pk.peng': 'stable-1',
'alexander.xie': 'stable-2',
'james.fu': 'stable-3',
'edison.ma': 'stable-4',
};
class DeployTask {
constructor(config) {
this.id = config.id; // building id
this.branch = config.branch; // branch
this.committer = config.user; // committer
this.startTime = new Date().getTime();
// this.config = config
this.status = 'pending'; // pending, success, failed, deploying , deployed, undeployed
this.queryId = undefined;
this.deployInterval = undefined;
this.start();
}
donnotDeployToMyEnv() {
return document.getElementById('deployEnv')?.checked;
}
getDom() {
const taskDom = Array.from(document.getElementsByTagName('strong')).find(dom => dom.innerText === this.id);
return taskDom ? taskDom.parentNode.parentNode : null;
}
async start () {
try {
console.log('task ', this.id, ' start');
this.status = 'pending';
await this.query();
await this.queueForDeply();
console.log('task is deploying ', this.id);
this.status = 'deploying';
await this.deploy();
this.stop();
} catch (error) {
console.log('deploy error ', error);
this.stop();
}
}
async query() {
return new Promise((resolve, reject) => {
this.queryId = setInterval(() => {
console.log(this.id, ' building status query ');
const dom = this.getDom();
if (!dom) {
return;
}
console.log('task is pending', this.id);
const status = dom.getElementsByClassName('anticon')[0].className.split(' ');
if (status.some(classItem => ['anticon-check-circle'].includes(classItem))) {
clearInterval(this.queryId);
this.status = 'success';
resolve();
}
if (status.some(classItem => ['anticon-close-circle'].includes(classItem))) {
clearInterval(this.queryId);
this.status = 'failed';
reject();
}
}, 1000);
});
}
async queueForDeply () {
return new Promise((resolve, reject) => {
console.log('queue for deploy ', this.id);
this.deployInterval = setInterval(() => {
if (monitor.canDeploy()) {
clearInterval(this.deployInterval);
resolve();
}
}, 500);
});
}
async deploy() {
const dom = this.getDom();
let deployed = false;
if (!dom) {
return;
}
dom.click();
await sleep(3000);
if (!document.getElementsByClassName('ant-dropdown')[0] || getComputedStyle( document.getElementsByClassName('ant-dropdown')[0]).display === 'none') {
document.querySelector('.publish-btn-check button').click();
}
await sleep(1000);
Array.from(document.getElementsByClassName('fast-publish-btn-menu')[0].getElementsByClassName('ant-dropdown-menu-item-group')).forEach(envDom => {
const envName = envDom.getElementsByClassName('ant-dropdown-menu-item-group-title')[0].innerText.toLocaleLowerCase().trim();
if (this.branch.includes(envName) ) {
// stg、pre分支发对应的环境
envDom.getElementsByClassName('ant-space-item')[0].click();
} else if (['stg', 'pre'].includes(envName.toLocaleLowerCase()) && !this.donnotDeployToMyEnv()) {
// 特性分支 发stg和pre对应的个人环境
const deployName = committerDeployMap[this.committer];
// envDom.getElementsByClassName('ant-space-item')[0].click()
Array.from(envDom.getElementsByClassName('ant-space-item')).forEach(dployDom => {
if (dployDom.innerText === deployName) {
dployDom.click();
}
});
}
deployed = true;
// env.getElementsByClassName('ant-space-item')
});
await sleep(300);
document.querySelector('.publish-btn-check button').click();
if (deployed) {
this.status = 'deployed';
return Promise.resolve();
}
this.status = 'undeployed';
return Promise.reject();
}
stop() {
// deployed, failed
console.log('monitor ', this.queryId, ' stop');
clearInterval(this.queryId);
clearInterval(this.deployInterval);
}
}
class DeloyMonitor {
constructor() {
this.tasksQueue = [];
this.monitorId = undefined;
this.hibernateId = undefined;
}
getTaskNodes () {
let count = 0;
let queryIntervalId;
const getNodes = () => {
try {
const parentNode = document.getElementsByClassName('task-list-sider__list')[0];
const nodes = parentNode.getElementsByClassName('task-card');
return nodes;
} catch (error) {
return null;
}
};
return new Promise((resolve, reject) => {
queryIntervalId = setInterval(() => {
count++;
const nodesout = getNodes();
if (nodesout) {
clearInterval(this.hibernateId);
return resolve(nodesout);
}
if (count > 60) {
clearInterval(queryIntervalId);
return reject();
}
}, 1000);
});
}
parseNode(node) {
const id = node.getElementsByClassName('first-line')[0].getElementsByTagName('strong')[0].innerText;
const branch = node.getElementsByClassName('branch')[0].innerText.trim();
const user = node.getElementsByClassName('second-line')[0].getElementsByTagName('strong')[0].innerText.split(' ')[1];
// const status = node.className.split(' ')[1];
// pending : anticon-sync anticon-spin
// success : anticon-check-circle
// failed : anticon-close-circle
// deployed : anticon-flag
const status = node.getElementsByClassName('anticon')[0].className.split(' ');
// console.log('id', id, 'status', status)
return { id, branch, user, status };
}
getCurrentUser() {
try {
const myCookie = document.cookie.split(';').reduce((acc, cur) => {
const [key, value] = cur.trim().split('=');
acc[key] = value;
return acc;
}, {});
const jsonStr = decodeURIComponent(myCookie?.sensorsdata2015jssdkcross || '{}');
const user = JSON.parse(jsonStr)?.distinct_id;
return user;
} catch (error) {
console.log('parse cookie error', error);
}
}
addToggleCheckbox() {
// getStorage setStorage
if(document.getElementById('deployEnv')) {
return;
}
const container = document.createElement('div');
const notDeployToMyEnv = getStorage('not_deploy_to_my_env') === 'no'; //default yes
const domStr = `
<div style="position: fixed; top: 150px; right: 40px;">
<input type="checkbox" id="deployEnv" name="deployEnv" value="Bike" ${notDeployToMyEnv ? 'checked' : ''}>
<label for="deployEnv">不发布特性分支到个人环境</label>
</div>
`;
container.innerHTML = domStr;
container.addEventListener('click', (e) => {
// console.log(e);
const { checked } = e?.target || {};
setStorage('not_deploy_to_my_env', checked ? 'no' : 'yes');
});
document.getElementById('root').appendChild(container);
}
donnotDeployToMyEnv() {
return document.getElementById('deployEnv').checked;
}
start () {
if (this.monitorId) {
return console.error('DeloyMonitor has already runned');
}
this.monitorId = setInterval(async () => {
console.log('deloyMonitor is running');
if (!navigator.onLine) {
this.hibernate();
}
try {
this.addToggleCheckbox();
const taskNodes = await this.getTaskNodes();
console.log('taskNodes 999', taskNodes);
if (!taskNodes) {
setTimeout(this.reload, 1000);
}
Array.from(taskNodes).forEach(node => {
const { id, branch, user, status } = this.parseNode(node);
console.log('task user', user, this.getCurrentUser());
// add new task
const task = this.tasksQueue.find(item => item.id === id);
const isStgPre = branch.includes('stg') || branch.includes('pre');
const waitingDeploy = status.some(statusClass => ['anticon-sync', 'anticon-spin'].includes(statusClass));
const isMyPush = user === this.getCurrentUser();
// if (isStgPre && !task && waitingDeploy && isMyPush) {
if (isMyPush && !task && waitingDeploy) {
const task = new DeployTask({ id, branch, user, status });
console.log('add new task ', id , task);
this.tasksQueue.push(task);
}
// remove when failed , deployed
if (status.some(statusClass => ['anticon-close-circle', 'anticon-flag'].includes(statusClass))) {
const taskIndex = this.tasksQueue.findIndex(item => item.id === id);
taskIndex !== -1 && console.log('will remove task ', id , task, 'taskIndex', taskIndex);
if (taskIndex !== -1) {
task.stop();
this.tasksQueue.splice(taskIndex, 1);
}
}
// remove when success but undeployed
if (status.some(statusClass => ['anticon-check-circle'].includes(statusClass))) {
const taskIndex = this.tasksQueue.findIndex(item => item.id === id);
const successTask = this.tasksQueue[taskIndex];
if (['undeployed', 'pending'].includes(successTask?.status)) {
successTask.stop();
this.tasksQueue.splice(taskIndex, 1);
}
}
});
} catch (err) {
this.reload();
} finally {
console.clear();
const timespan = parseInt((new Date().getTime() - startTime) / 1000);
console.log('at time', timespan , 'tasks are ',[...this.tasksQueue]);
timespan > 3600 && this.reload();
}
}, 5000);
}
stop () {
console.log('monitor stop');
this.tasksQueue.forEach(task => task.stop());
clearInterval(this.monitorId);
this.monitorId = undefined;
}
canDeploy (){
return !this.tasksQueue.some(task => task.status === 'deploying');
}
reload() {
window.location.reload();
}
hibernate () {
console.log('hibernate');
this.stop();
this.tasksQueue = [];
this.hibernateId = setInterval(() => {
if (navigator.onLine) {
this.start();
clearInterval(this.hibernateId);
this.hibernateId = undefined;
}
}, 60 * 1000);
}
}
monitor = new DeloyMonitor();
monitor.start();
window.onbeforeunload = function(e) {
monitor.stop();
};
window.addEventListener('online', () => monitor.start());
window.addEventListener('offline', () => monitor.hibernate());
})();