Skip to content

Commit aeb2e9c

Browse files
[PE-2763] Add PR title JIRA check workflow (#81)
1 parent b0b29db commit aeb2e9c

1 file changed

Lines changed: 130 additions & 0 deletions

File tree

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
name: Enforce Jira Ticket in PR Title
2+
3+
on:
4+
pull_request:
5+
types:
6+
- opened
7+
- edited
8+
- reopened
9+
10+
env:
11+
ALLOWED_PATTERN: '^\[((PE|BC|INFRA|SEC|PLAT|SE)-[0-9]+|FIX|CHORE)\] .+'
12+
COMMENT_MARKER: '<!-- pr-title-jira-check -->'
13+
14+
jobs:
15+
check-pr-title:
16+
name: PR title contains Jira ticket
17+
runs-on: ubuntu-latest
18+
if: github.actor != 'dependabot[bot]'
19+
permissions:
20+
pull-requests: write
21+
22+
steps:
23+
- name: Check PR title format
24+
id: pr-check
25+
env:
26+
PR_TITLE: ${{ github.event.pull_request.title }}
27+
run: |
28+
echo "PR Title : $PR_TITLE"
29+
echo "Pattern : $ALLOWED_PATTERN"
30+
31+
if [[ "$PR_TITLE" =~ $ALLOWED_PATTERN ]]; then
32+
echo "✅ PR title is valid"
33+
echo "found=true" >> $GITHUB_OUTPUT
34+
else
35+
echo "❌ PR title is invalid — see PR comment for expected formats"
36+
echo "found=false" >> $GITHUB_OUTPUT
37+
fi
38+
39+
- name: Manage PR title check comment
40+
uses: actions/github-script@v9
41+
env:
42+
COMMENT_MARKER: ${{ env.COMMENT_MARKER }}
43+
with:
44+
github-token: ${{ secrets.GITHUB_TOKEN }}
45+
script: |
46+
const marker = process.env.COMMENT_MARKER;
47+
const prTitle = context.payload.pull_request.title;
48+
const isValid = '${{ steps.pr-check.outputs.found }}' === 'true';
49+
const { owner, repo } = context.repo;
50+
const issue_number = context.issue.number;
51+
const repoParams = { owner, repo };
52+
53+
const titleFormats = [
54+
['[PE-XXXXX] Description', '[PE-12345] fix login bug'],
55+
['[BC-XXXXX] Description', '[BC-12345] fix login bug'],
56+
['[INFRA-XXXXX] Description', '[INFRA-12345] patch timeout'],
57+
['[SEC-XXXXX] Description', '[SEC-12345] security patch'],
58+
['[PLAT-XXXXX] Description', '[PLAT-12345] platform update'],
59+
['[SE-XXXXX] Description', '[SE-12345] fix login bug'],
60+
['[FIX] Description', '[FIX] fix login bug'],
61+
['[CHORE] Description', '[CHORE] update dependencies'],
62+
['[BC-XXXXX] Description (#PR)', '[BC-12345] fix login bug (#456)'],
63+
];
64+
65+
const formatTable = [
66+
'| Format | Example |',
67+
'|--------|---------|',
68+
...titleFormats.map(([format, example]) => `| \`${format}\` | \`${example}\` |`),
69+
].join('\n');
70+
71+
const failureBody = [
72+
marker,
73+
`## ❌ Invalid PR Title: \`${prTitle}\``,
74+
'',
75+
'**PR title must match one of the following formats:**',
76+
'',
77+
formatTable,
78+
'',
79+
'**How to fix:**',
80+
'- Click the **Edit** button next to your PR title',
81+
'- Update it to match one of the formats above',
82+
'- The check re-runs automatically once the title is updated.',
83+
].join('\n');
84+
85+
const successBody = [
86+
marker,
87+
`## ✅ PR Title Valid: \`${prTitle}\``,
88+
'',
89+
'Jira ticket reference found. This PR is good to merge.',
90+
].join('\n');
91+
92+
const findMarkedComment = async () => {
93+
const { data: comments } = await github.rest.issues.listComments({
94+
...repoParams,
95+
issue_number,
96+
});
97+
return comments.find((c) => c.body?.includes(marker));
98+
};
99+
100+
const upsertComment = async (commentId, body) => {
101+
if (commentId) {
102+
await github.rest.issues.updateComment({
103+
...repoParams,
104+
comment_id: commentId,
105+
body,
106+
});
107+
console.log(`Updated comment ${commentId}`);
108+
return;
109+
}
110+
111+
await github.rest.issues.createComment({
112+
...repoParams,
113+
issue_number,
114+
body,
115+
});
116+
console.log('Created new comment');
117+
};
118+
119+
const existing = await findMarkedComment();
120+
const commentId = existing?.id;
121+
122+
if (!isValid) {
123+
await upsertComment(commentId, failureBody);
124+
core.setFailed('PR title does not match required format');
125+
return;
126+
}
127+
128+
if (commentId) {
129+
await upsertComment(commentId, successBody);
130+
}

0 commit comments

Comments
 (0)