-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLulaGitlabCIProgressTrack.ts
More file actions
168 lines (152 loc) · 7.12 KB
/
LulaGitlabCIProgressTrack.ts
File metadata and controls
168 lines (152 loc) · 7.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// ==UserScript==
// @name:zh-tw GitLab CI/CD Telegram 進度追蹤 - Lula
// @name GitLab CI/CD Telegram Progress Notifier - Lula
// @namespace com.sherryyue.lulagitlabciprogresstrack
// @version 0.5
// @description:zh-tw 此腳本在 GitLab CI/CD 中發送部署成功與失敗通知到 Telegram 頻道。它會通知您流水線和工作的成功或失敗,讓您在離開時能夠在手機或其他裝置上接收更新。
// @description This script sends notifications to a Telegram channel on GitLab CI/CD. It notifies you of the success or failure of pipelines and jobs, allowing you to receive updates on your mobile device or other devices while you are away.
// @author SherryYue
// @copyright SherryYue
// @license MIT
// @match http://gitlab.lulainno.com/*
// @supportURL sherryyue.c@protonmail.com
// @icon https://sherryyuechiu.github.io/card/images/logo/maskable_icon_x96.png
// @require https://code.jquery.com/jquery-3.6.0.js
// @require https://code.jquery.com/ui/1.13.1/jquery-ui.js
// @supportURL "https://github.com/sherryyuechiu/GreasyMonkeyScripts/issues"
// @homepage "https://github.com/sherryyuechiu/GreasyMonkeyScripts"
// @grant GM_addStyle
// ==/UserScript==
(function () {
const TGToken: string = '6029836287:AAGtY81VFypCB976DwfRG7hZ033oEHzBT1Y';
const chatId: number = 901947307;
let pipelineState: { [key: string]: string } = {};
let pipelineEverUpdate: { [key: string]: boolean } = {};
let prevUndoneAmount: number = -1, prevSuccessAmount: number = -1, prevFailedAmount: number = -1;
const PIPELINE_STATE = {
PENDING: 'pending',
RUNNING: 'running',
SUCCESS: 'success',
FAILED: 'failed',
PARTIAL_SUCCESS: 'success-with-warnings',
PARTIAL_FAILED: 'failed-with-warnings',
WAITING_RESOURCES: 'waiting-for-resource',
};
function judgeState(elm: HTMLElement): string | undefined {
const classList = elm.classList;
if (classList.contains('ci-pending')) return PIPELINE_STATE.PENDING;
if (classList.contains('ci-running')) return PIPELINE_STATE.RUNNING;
if (classList.contains('ci-success')) return PIPELINE_STATE.SUCCESS;
if (classList.contains('ci-failed')) return PIPELINE_STATE.FAILED;
if (classList.contains('ci-success-with-warnings')) return PIPELINE_STATE.PARTIAL_SUCCESS;
if (classList.contains('ci-failed-with-warnings')) return PIPELINE_STATE.PARTIAL_FAILED;
if (classList.contains('ci-waiting-for-resource')) return PIPELINE_STATE.WAITING_RESOURCES;
}
function getPipeLineId(elm: HTMLElement): string {
return elm.querySelector<HTMLElement>('.pipeline-tags [data-qa-selector="pipeline_url_link"]')?.innerText.trim() || '';
}
function sendNotification(msg: string): void {
const searchParams = new URLSearchParams({
chat_id: chatId.toString(),
parse_mode: 'MarkdownV2',
text: msg,
});
const requestUrl = new URL(`https://api.telegram.org/bot${TGToken}/sendMessage`);
requestUrl.search = searchParams.toString().replace(/%25/g, '%');
fetch(requestUrl.toString());
}
function isHTMLElement(obj: any): obj is HTMLElement {
return obj?.nodeType === 1;
}
function main(): void {
const pipelines = document.querySelectorAll('.commit[data-testid=pipeline-table-row]');
if (Object.keys(pipelineState).length === 0) {
pipelines.forEach((pipeline) => {
const statusTd = pipeline.querySelector('[data-label=Status] [data-qa-selector=pipeline_commit_status]') as HTMLElement;
pipelineState[getPipeLineId(pipeline as HTMLElement)] = judgeState(statusTd) || '';
pipelineEverUpdate[getPipeLineId(pipeline as HTMLElement)] = false;
});
return;
}
let diffPipelines: HTMLElement[] = [];
pipelines.forEach((pipeline) => {
if (!isHTMLElement(pipeline)) return;
const statusTd = pipeline.querySelector('[data-label=Status] [data-qa-selector=pipeline_commit_status]') as HTMLElement;
if (statusTd) {
if (pipelineState[getPipeLineId(pipeline)] === judgeState(statusTd)) {
return;
}
console.log('changed', getPipeLineId(pipeline), pipelineState[getPipeLineId(pipeline)], judgeState(statusTd));
diffPipelines.push(pipeline as HTMLElement);
}
});
if (diffPipelines.length === 0) return;
let successAmount = 0;
let failedAmount = 0;
let undoneAmount = 0;
pipelines.forEach((pipeline) => {
const statusTd = pipeline.querySelector('[data-label=Status] [data-qa-selector=pipeline_commit_status]') as HTMLElement;
if (statusTd) {
const state = judgeState(statusTd);
if (
(state === PIPELINE_STATE.SUCCESS || state === PIPELINE_STATE.PARTIAL_SUCCESS) &&
pipelineEverUpdate[getPipeLineId(pipeline as HTMLElement)] === true &&
pipelineState[getPipeLineId(pipeline as HTMLElement)] !== judgeState(statusTd)
) successAmount++;
else if (
(state === PIPELINE_STATE.FAILED || state === PIPELINE_STATE.PARTIAL_FAILED) &&
pipelineEverUpdate[getPipeLineId(pipeline as HTMLElement)] === true &&
pipelineState[getPipeLineId(pipeline as HTMLElement)] !== judgeState(statusTd)
) failedAmount++;
else if (
(state === PIPELINE_STATE.RUNNING || state === PIPELINE_STATE.PENDING || state === PIPELINE_STATE.WAITING_RESOURCES)
) undoneAmount++;
}
});
if (
prevUndoneAmount < undoneAmount ||
prevSuccessAmount < successAmount ||
prevFailedAmount < failedAmount
) {
const LINE_BREAK = '\%0A';
const projectName = document.head.querySelector('title')?.text.split(' · ')?.[1] || '';
let message = `CICD狀態更新:${projectName}`;
if (undoneAmount > 0) message += LINE_BREAK + `還有${undoneAmount}項未完成`;
else {
if (failedAmount > 0) {
message += LINE_BREAK + `全數完成,但有${failedAmount}項失敗`;
} else if (undoneAmount === 0) {
message += LINE_BREAK + `全數完成`;
}
}
console.log('sent message: ', message);
sendNotification(message);
prevUndoneAmount = undoneAmount;
prevSuccessAmount = successAmount;
prevFailedAmount = failedAmount;
}
pipelines.forEach((pipeline) => {
const statusTd = pipeline.querySelector('[data-label=Status] [data-qa-selector=pipeline_commit_status]') as HTMLElement;
if (pipelineState[getPipeLineId(pipeline as HTMLElement)] !== judgeState(statusTd)) {
pipelineState[getPipeLineId(pipeline as HTMLElement)] = judgeState(statusTd) || '';
pipelineEverUpdate[getPipeLineId(pipeline as HTMLElement)] = true;
}
});
console.log('state of pipelines', pipelineState);
}
const observer = new MutationObserver((mutations, obs) => {
main();
});
observer.observe(document.querySelector("body") as Node, {
childList: true,
subtree: true
});
if (window.location.href.endsWith('/pipelines')) {
main();
}
document.getElementsByTagName('head')[0].append(
'<link '
+ 'href="//code.jquery.com/ui/1.13.1/themes/base/jquery-ui.css" '
+ ' type="text/css">'
);
})();