Van Deploy

Auto Deploy for Van!

目前为 2022-08-23 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Van Deploy
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.5.4
  5. // @description Auto Deploy for Van!
  6. // @author Alexander
  7. // @match https://van.huolala.work/projects/835/*
  8. // @match https://van.huolala.work/projects/833/*
  9. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  10. // @grant none
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14.  
  15. const getStorage = (name) => {
  16. // const cookieObj = document.cookie.split(';').reduce((acc, cur) => {
  17. // const [key, value] = cur.split('=');
  18. // acc[key.trim()] = value;
  19. // return acc;
  20. // } , {})
  21. // return cookieObj[name || ''] || '';
  22. return localStorage.getItem(name || '') || '';
  23. };
  24.  
  25. const setStorage = (name, value) => {
  26. // const cookieObj = document.cookie.split(';').reduce((acc, cur) => {
  27. // const [key, value] = cur.split('=');
  28. // acc[key.trim()] = value;
  29. // return acc;
  30. // } , {})
  31. // cookieObj[name] = value;
  32. // const newCookie = Object.keys(cookieObj).map(i => `${i}=${cookieObj[i]}`).join(';');
  33. // document.cookie = newCookie;
  34. localStorage.setItem(name, value);
  35. };
  36.  
  37. (function() {
  38. 'use strict';
  39. // console.log(' van deploy is running ')
  40. let monitor;
  41. const startTime = new Date().getTime();
  42. const sleep = (time = 1000) => new Promise(resolve => setTimeout(resolve, time));
  43.  
  44. const committerDeployMap = {
  45. 'pk.peng': 'stable-1',
  46. 'alexander.xie': 'stable-2',
  47. 'james.fu': 'stable-3',
  48. 'edison.ma': 'stable-4',
  49. };
  50.  
  51.  
  52. class DeployTask {
  53. constructor(config) {
  54. this.id = config.id; // building id
  55. this.branch = config.branch; // branch
  56. this.committer = config.user; // committer
  57. this.startTime = new Date().getTime();
  58. // this.config = config
  59. this.status = 'pending'; // pending, success, failed, deploying , deployed, undeployed
  60. this.queryId = undefined;
  61. this.deployInterval = undefined;
  62. this.start();
  63. }
  64.  
  65. donnotDeployToMyEnv() {
  66. return document.getElementById('deployEnv')?.checked;
  67. }
  68.  
  69. getDom() {
  70. const taskDom = Array.from(document.getElementsByTagName('strong')).find(dom => dom.innerText === this.id);
  71. return taskDom ? taskDom.parentNode.parentNode : null;
  72. }
  73.  
  74. async start () {
  75. try {
  76. console.log('task ', this.id, ' start');
  77. this.status = 'pending';
  78. await this.query();
  79. await this.queueForDeply();
  80. console.log('task is deploying ', this.id);
  81. this.status = 'deploying';
  82. await this.deploy();
  83. this.stop();
  84. } catch (error) {
  85. console.log('deploy error ', error);
  86. this.stop();
  87. }
  88. }
  89.  
  90. async query() {
  91. return new Promise((resolve, reject) => {
  92.  
  93. this.queryId = setInterval(() => {
  94. console.log(this.id, ' building status query ');
  95. const dom = this.getDom();
  96. if (!dom) {
  97. return;
  98. }
  99. console.log('task is pending', this.id);
  100. const status = dom.getElementsByClassName('anticon')[0].className.split(' ');
  101.  
  102. if (status.some(classItem => ['anticon-check-circle'].includes(classItem))) {
  103. clearInterval(this.queryId);
  104. this.status = 'success';
  105. resolve();
  106. }
  107. if (status.some(classItem => ['anticon-close-circle'].includes(classItem))) {
  108. clearInterval(this.queryId);
  109. this.status = 'failed';
  110. reject();
  111. }
  112. }, 1000);
  113. });
  114. }
  115.  
  116. async queueForDeply () {
  117. return new Promise((resolve, reject) => {
  118. console.log('queue for deploy ', this.id);
  119. this.deployInterval = setInterval(() => {
  120. if (monitor.canDeploy()) {
  121. clearInterval(this.deployInterval);
  122. resolve();
  123. }
  124. }, 500);
  125. });
  126. }
  127.  
  128. async deploy() {
  129. const dom = this.getDom();
  130. let deployed = false;
  131. if (!dom) {
  132. return;
  133. }
  134. dom.click();
  135. await sleep(3000);
  136. if (!document.getElementsByClassName('ant-dropdown')[0] || getComputedStyle( document.getElementsByClassName('ant-dropdown')[0]).display === 'none') {
  137. document.querySelector('.publish-btn-check button').click();
  138. }
  139.  
  140. await sleep(1000);
  141.  
  142. Array.from(document.getElementsByClassName('fast-publish-btn-menu')[0].getElementsByClassName('ant-dropdown-menu-item-group')).forEach(envDom => {
  143. const envName = envDom.getElementsByClassName('ant-dropdown-menu-item-group-title')[0].innerText.toLocaleLowerCase().trim();
  144.  
  145. if (this.branch.includes(envName) ) {
  146. // stg、pre分支发对应的环境
  147. envDom.getElementsByClassName('ant-space-item')[0].click();
  148. } else if (['stg', 'pre'].includes(envName.toLocaleLowerCase()) && !this.donnotDeployToMyEnv()) {
  149. // 特性分支 发stg和pre对应的个人环境
  150. const deployName = committerDeployMap[this.committer];
  151. // envDom.getElementsByClassName('ant-space-item')[0].click()
  152. Array.from(envDom.getElementsByClassName('ant-space-item')).forEach(dployDom => {
  153. if (dployDom.innerText === deployName) {
  154. dployDom.click();
  155. }
  156. });
  157. }
  158. deployed = true;
  159. // env.getElementsByClassName('ant-space-item')
  160. });
  161. await sleep(300);
  162.  
  163. document.querySelector('.publish-btn-check button').click();
  164. if (deployed) {
  165. this.status = 'deployed';
  166. return Promise.resolve();
  167. }
  168. this.status = 'undeployed';
  169. return Promise.reject();
  170. }
  171.  
  172.  
  173. stop() {
  174. // deployed, failed
  175. console.log('monitor ', this.queryId, ' stop');
  176. clearInterval(this.queryId);
  177. clearInterval(this.deployInterval);
  178. }
  179. }
  180.  
  181.  
  182. class DeloyMonitor {
  183. constructor() {
  184. this.tasksQueue = [];
  185. this.monitorId = undefined;
  186. this.hibernateId = undefined;
  187. }
  188.  
  189. getTaskNodes () {
  190. let count = 0;
  191. let queryIntervalId;
  192. const getNodes = () => {
  193. try {
  194. const parentNode = document.getElementsByClassName('task-list-sider__list')[0];
  195. const nodes = parentNode.getElementsByClassName('task-card');
  196. return nodes;
  197. } catch (error) {
  198. return null;
  199. }
  200. };
  201.  
  202. return new Promise((resolve, reject) => {
  203. queryIntervalId = setInterval(() => {
  204. count++;
  205. const nodesout = getNodes();
  206. if (nodesout) {
  207. clearInterval(this.hibernateId);
  208. return resolve(nodesout);
  209. }
  210. if (count > 60) {
  211. clearInterval(queryIntervalId);
  212. return reject();
  213. }
  214. }, 1000);
  215. });
  216. }
  217.  
  218. parseNode(node) {
  219. const id = node.getElementsByClassName('first-line')[0].getElementsByTagName('strong')[0].innerText;
  220. const branch = node.getElementsByClassName('branch')[0].innerText.trim();
  221. const user = node.getElementsByClassName('second-line')[0].getElementsByTagName('strong')[0].innerText.split(' ')[1];
  222. // const status = node.className.split(' ')[1];
  223. // pending : anticon-sync anticon-spin
  224. // success : anticon-check-circle
  225. // failed : anticon-close-circle
  226. // deployed : anticon-flag
  227. const status = node.getElementsByClassName('anticon')[0].className.split(' ');
  228. // console.log('id', id, 'status', status)
  229. return { id, branch, user, status };
  230. }
  231.  
  232. getCurrentUser() {
  233. try {
  234. const myCookie = document.cookie.split(';').reduce((acc, cur) => {
  235. const [key, value] = cur.trim().split('=');
  236. acc[key] = value;
  237. return acc;
  238. }, {});
  239. const jsonStr = decodeURIComponent(myCookie?.sensorsdata2015jssdkcross || '{}');
  240. const user = JSON.parse(jsonStr)?.distinct_id;
  241. return user;
  242. } catch (error) {
  243. console.log('parse cookie error', error);
  244. }
  245. }
  246.  
  247. addToggleCheckbox() {
  248. // getStorage setStorage
  249. if(document.getElementById('deployEnv')) {
  250. return;
  251. }
  252. const container = document.createElement('div');
  253. const notDeployToMyEnv = getStorage('not_deploy_to_my_env') === 'no'; //default yes
  254. const domStr = `
  255. <div style="position: fixed; top: 150px; right: 40px;">
  256. <input type="checkbox" id="deployEnv" name="deployEnv" value="Bike" ${notDeployToMyEnv ? 'checked' : ''}>
  257. <label for="deployEnv">不发布特性分支到个人环境</label>
  258. </div>
  259. `;
  260. container.innerHTML = domStr;
  261. container.addEventListener('click', (e) => {
  262. // console.log(e);
  263. const { checked } = e?.target || {};
  264. setStorage('not_deploy_to_my_env', checked ? 'no' : 'yes');
  265. });
  266. document.getElementById('root').appendChild(container);
  267. }
  268.  
  269.  
  270. donnotDeployToMyEnv() {
  271. return document.getElementById('deployEnv').checked;
  272. }
  273.  
  274. start () {
  275. if (this.monitorId) {
  276. return console.error('DeloyMonitor has already runned');
  277. }
  278.  
  279.  
  280. this.monitorId = setInterval(async () => {
  281. console.log('deloyMonitor is running');
  282. if (!navigator.onLine) {
  283. this.hibernate();
  284. }
  285. try {
  286. this.addToggleCheckbox();
  287.  
  288. const taskNodes = await this.getTaskNodes();
  289. console.log('taskNodes 999', taskNodes);
  290. if (!taskNodes) {
  291. setTimeout(this.reload, 1000);
  292. }
  293. Array.from(taskNodes).forEach(node => {
  294.  
  295. const { id, branch, user, status } = this.parseNode(node);
  296. console.log('task user', user, this.getCurrentUser());
  297. // add new task
  298. const task = this.tasksQueue.find(item => item.id === id);
  299. const isStgPre = branch.includes('stg') || branch.includes('pre');
  300. const waitingDeploy = status.some(statusClass => ['anticon-sync', 'anticon-spin'].includes(statusClass));
  301. const isMyPush = user === this.getCurrentUser();
  302. // if (isStgPre && !task && waitingDeploy && isMyPush) {
  303. if (isMyPush && !task && waitingDeploy) {
  304.  
  305. const task = new DeployTask({ id, branch, user, status });
  306. console.log('add new task ', id , task);
  307. this.tasksQueue.push(task);
  308. }
  309.  
  310. // remove when failed , deployed
  311. if (status.some(statusClass => ['anticon-close-circle', 'anticon-flag'].includes(statusClass))) {
  312. const taskIndex = this.tasksQueue.findIndex(item => item.id === id);
  313. taskIndex !== -1 && console.log('will remove task ', id , task, 'taskIndex', taskIndex);
  314. if (taskIndex !== -1) {
  315. task.stop();
  316. this.tasksQueue.splice(taskIndex, 1);
  317. }
  318. }
  319.  
  320. // remove when success but undeployed
  321. if (status.some(statusClass => ['anticon-check-circle'].includes(statusClass))) {
  322. const taskIndex = this.tasksQueue.findIndex(item => item.id === id);
  323. const successTask = this.tasksQueue[taskIndex];
  324. if (['undeployed', 'pending'].includes(successTask?.status)) {
  325. successTask.stop();
  326. this.tasksQueue.splice(taskIndex, 1);
  327. }
  328. }
  329.  
  330. });
  331. } catch (err) {
  332. this.reload();
  333. } finally {
  334. console.clear();
  335. const timespan = parseInt((new Date().getTime() - startTime) / 1000);
  336. console.log('at time', timespan , 'tasks are ',[...this.tasksQueue]);
  337. timespan > 3600 && this.reload();
  338. }
  339. }, 5000);
  340. }
  341.  
  342. stop () {
  343. console.log('monitor stop');
  344. this.tasksQueue.forEach(task => task.stop());
  345. clearInterval(this.monitorId);
  346. this.monitorId = undefined;
  347. }
  348. canDeploy (){
  349. return !this.tasksQueue.some(task => task.status === 'deploying');
  350. }
  351.  
  352. reload() {
  353. window.location.reload();
  354. }
  355.  
  356. hibernate () {
  357. console.log('hibernate');
  358. this.stop();
  359. this.tasksQueue = [];
  360. this.hibernateId = setInterval(() => {
  361. if (navigator.onLine) {
  362. this.start();
  363. clearInterval(this.hibernateId);
  364. this.hibernateId = undefined;
  365. }
  366.  
  367. }, 60 * 1000);
  368. }
  369. }
  370.  
  371. monitor = new DeloyMonitor();
  372. monitor.start();
  373.  
  374. window.onbeforeunload = function(e) {
  375. monitor.stop();
  376. };
  377.  
  378. window.addEventListener('online', () => monitor.start());
  379. window.addEventListener('offline', () => monitor.hibernate());
  380.  
  381.  
  382. })();