Skip to content

Commit 2450e43

Browse files
committed
feat: add master branch release pr merge actions
- bugfix: update fiel paths for test yml to work - add email send action as well KBDEV-1393
1 parent f655e19 commit 2450e43

6 files changed

Lines changed: 289 additions & 3 deletions

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/* eslint-disable @typescript-eslint/no-var-requires */
2+
const https = require('https');
3+
4+
function createJiraTicket() {
5+
const options = {
6+
method: 'POST',
7+
host: process.env.JIRA_BASE_URL,
8+
port: process.env.JIRA_PORT,
9+
path: '/jira/rest/api/2/issue/',
10+
headers: {
11+
Authorization: `Bearer ${process.env.JIRA_API_TOKEN}`,
12+
Accept: 'application/json',
13+
'Content-Type': 'application/json',
14+
},
15+
};
16+
const formattedTitle = `GraphKB Client ${process.env.PR_TITLE}`;
17+
let formattedDesc = '';
18+
19+
const body = process.env.PR_DESCRIPTION;
20+
const lines = body.split('\n').map((l) => l.trim()).filter(Boolean);
21+
22+
let section = '';
23+
const tables = {};
24+
25+
for (let line of lines) {
26+
if (line.startsWith('###')) {
27+
line = '';
28+
} else if (line.startsWith('**')) {
29+
section = line.replace(/\*\*/g, '').trim();
30+
tables[section] = [];
31+
} else if (line.startsWith('[[')) {
32+
const match = line.match(/\[\[(?<title>[A-Z]+-\d+)\]\([^)]+\)\]\s*-\s*(?<description>.+)/);
33+
34+
if (match?.groups) {
35+
const { title, description } = match.groups;
36+
tables[section].push({ title, description });
37+
}
38+
}
39+
}
40+
41+
for (const [sectionName, items] of Object.entries(tables)) {
42+
formattedDesc += `| *${sectionName}* | *Need Review* | *Status* | *Reviewer* | *Description* |\n`;
43+
44+
for (const { title, description } of items) {
45+
formattedDesc += `| ${title} | | | | ${description} |\n`;
46+
}
47+
}
48+
49+
const issueData = JSON.stringify({
50+
fields: {
51+
project: {
52+
key: process.env.JIRA_PROJECT_NAME,
53+
},
54+
summary: formattedTitle,
55+
description: formattedDesc,
56+
issuetype: {
57+
name: process.env.JIRA_ISSUE_TYPE,
58+
},
59+
},
60+
});
61+
62+
console.log('Writing issue: \n', issueData);
63+
64+
const req = https.request(options, (res) => {
65+
res.setEncoding('utf8');
66+
res.on('data', (body) => {
67+
console.log('Body:', body);
68+
});
69+
});
70+
71+
req.on('error', (e) => {
72+
console.error('problem with request:', e.message);
73+
});
74+
75+
req.write(issueData);
76+
req.end();
77+
}
78+
79+
createJiraTicket();

.github/scripts/sendPrEmail-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import https from 'https';
1+
const https = require('https');
22

33
const {
44
AZURE_TENANT_ID,
@@ -78,7 +78,7 @@ const formatBody = (body = 'No description provided.') => {
7878

7979
content = content.replace(
8080
/^### (.*)$/gm,
81-
'<div style="font-size:14px;font-weight:bold;">$1</div>'
81+
'<h2>$1</h2>'
8282
);
8383

8484
content = content.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');

.github/scripts/sendPrEmail.js

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
const https = require('https');
2+
3+
const {
4+
AZURE_TENANT_ID,
5+
AZURE_CLIENT_ID,
6+
AZURE_CLIENT_SECRET,
7+
AZURE_SENDER_EMAIL,
8+
RECIPIENT_EMAIL,
9+
PR_TITLE,
10+
PR_BODY
11+
} = process.env;
12+
13+
/**
14+
* Simple HTTPS request helper
15+
*/
16+
const httpsRequest = async (options, body) =>
17+
new Promise((resolve, reject) => {
18+
const req = https.request(options, (res) => {
19+
let data = '';
20+
res.on('data', (chunk) => (data += chunk));
21+
res.on('end', () => resolve({ status: res.statusCode, data }));
22+
});
23+
req.on('error', reject);
24+
if (body) req.write(body);
25+
req.end();
26+
});
27+
28+
/**
29+
* Get Microsoft Graph access token (client credentials)
30+
*/
31+
const getGraphToken = async () => {
32+
const params = new URLSearchParams({
33+
client_id: AZURE_CLIENT_ID,
34+
client_secret: AZURE_CLIENT_SECRET,
35+
scope: 'https://graph.microsoft.com/.default',
36+
grant_type: 'client_credentials'
37+
});
38+
39+
const { status, data } = await httpsRequest(
40+
{
41+
method: 'POST',
42+
hostname: 'login.microsoftonline.com',
43+
path: `/${AZURE_TENANT_ID}/oauth2/v2.0/token`,
44+
headers: {
45+
'Content-Type': 'application/x-www-form-urlencoded',
46+
'Content-Length': params.toString().length
47+
}
48+
},
49+
params.toString()
50+
);
51+
52+
const json = JSON.parse(data);
53+
54+
if (status >= 300 || !json.access_token) {
55+
throw new Error(`Token request failed: ${data}`);
56+
}
57+
58+
return json.access_token;
59+
};
60+
61+
/**
62+
* Escape HTML
63+
*/
64+
const escapeHtml = (text) =>
65+
text
66+
.replace(/&/g, '&amp;')
67+
.replace(/</g, '&lt;')
68+
.replace(/>/g, '&gt;');
69+
70+
/**
71+
* Minimal markdown formatter
72+
* - ### heading → bold, 14px
73+
* - **bold** → bold
74+
* - everything else → plain text
75+
*/
76+
const formatBody = (body = 'No description provided.') => {
77+
let content = escapeHtml(body);
78+
79+
content = content.replace(
80+
/^### (.*)$/gm,
81+
'<h2>$1</h2>'
82+
);
83+
84+
content = content.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
85+
86+
return content.replace(/\n/g, '<br/>');
87+
};
88+
89+
/**
90+
* Send email via Microsoft Graph
91+
*/
92+
const sendMail = async (token) => {
93+
const payload = JSON.stringify({
94+
message: {
95+
subject: `GraphKb Client ${PR_TITLE}`,
96+
body: {
97+
contentType: 'HTML',
98+
content: formatBody(PR_BODY)
99+
},
100+
toRecipients: [
101+
{
102+
emailAddress: { address: RECIPIENT_EMAIL }
103+
}
104+
]
105+
}
106+
});
107+
108+
const { status, data } = await httpsRequest({
109+
method: 'POST',
110+
hostname: 'graph.microsoft.com',
111+
path: `/v1.0/users/${AZURE_SENDER_EMAIL}/sendMail`,
112+
headers: {
113+
Authorization: `Bearer ${token}`,
114+
'Content-Type': 'application/json',
115+
'Content-Length': Buffer.byteLength(payload)
116+
}
117+
}, payload);
118+
119+
if (status >= 300) {
120+
throw new Error(`Graph sendMail failed (${status}): ${data}`);
121+
}
122+
};
123+
124+
/**
125+
* Main
126+
*/
127+
const main = async () => {
128+
const token = await getGraphToken();
129+
await sendMail(token);
130+
console.log('Email sent successfully.');
131+
};
132+
133+
main().catch((err) => {
134+
console.error(err);
135+
process.exit(1);
136+
});
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Email on master merge
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- master
7+
types: [closed]
8+
9+
jobs:
10+
send-email:
11+
if: github.event.pull_request.merged == true
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Check out code
16+
uses: actions/checkout@v4
17+
18+
- name: Set up Node.js
19+
uses: actions/setup-node@v3
20+
with:
21+
node-version: 20
22+
23+
- name: Send PR email
24+
run: node .github/scripts/sendPrEmail.js
25+
env:
26+
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
27+
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
28+
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
29+
AZURE_SENDER_EMAIL: ${{ secrets.AZURE_SENDER_EMAIL }}
30+
RECIPIENT_EMAIL: ${{ vars.OUTLOOK_TARGET_EMAIL }}
31+
PR_TITLE: ${{ github.event.pull_request.title }}
32+
PR_BODY: ${{ github.event.pull_request.body }}

.github/workflows/email-test.yml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,16 @@ jobs:
1212
runs-on: ubuntu-latest
1313

1414
steps:
15+
- name: Check out code
16+
uses: actions/checkout@v4
17+
18+
- name: Set up Node.js
19+
uses: actions/setup-node@v3
20+
with:
21+
node-version: 20
22+
1523
- name: Send PR email
16-
run: node .github/scripts/sendPrEmail.js
24+
run: node .github/scripts/sendPrEmail-test.js
1725
env:
1826
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
1927
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Create JIRA ticket on release PR creation
2+
3+
on:
4+
pull_request:
5+
types: [opened]
6+
branches:
7+
- master
8+
9+
jobs:
10+
create_ticket:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- name: Check out code
15+
uses: actions/checkout@v4
16+
17+
- name: Set up Node.js
18+
uses: actions/setup-node@v3
19+
with:
20+
node-version: 20
21+
22+
- name: Run script
23+
run: node .github/scripts/create-jira-ticket.js
24+
env:
25+
JIRA_PROJECT_NAME: ${{ vars.JIRA_PROJECT_NAME }}
26+
JIRA_ISSUE_TYPE: ${{ vars.JIRA_ISSUE_TYPE }}
27+
JIRA_BASE_URL: ${{ vars.JIRA_BASE_URL }}
28+
JIRA_PORT: ${{ vars.JIRA_PORT }}
29+
JIRA_API_TOKEN: ${{ secrets.JACLI_JIRA_TOKEN }}
30+
PR_TITLE: ${{ github.event.pull_request.title }}
31+
PR_DESCRIPTION: ${{ github.event.pull_request.body }}

0 commit comments

Comments
 (0)