Skip to content

Commit 35a6e22

Browse files
committed
chore: add GitHub Action to create Jira issues on new GitHub issues
1 parent 7bbaa07 commit 35a6e22

1 file changed

Lines changed: 165 additions & 0 deletions

File tree

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
name: Create Jira ticket on GitHub issue open
2+
3+
on:
4+
issues:
5+
types: [opened]
6+
7+
permissions:
8+
contents: read
9+
issues: write
10+
11+
jobs:
12+
create-jira-ticket:
13+
runs-on: ubuntu-latest
14+
environment: jira-codex
15+
steps:
16+
- name: Validate Jira configuration
17+
env:
18+
JIRA_BASE_URL: ${{ vars.JIRA_BASE_URL }}
19+
JIRA_PROJECT_KEY: ${{ vars.JIRA_PROJECT_KEY }}
20+
JIRA_ISSUE_TYPE: ${{ vars.JIRA_ISSUE_TYPE }}
21+
JIRA_EMAIL: ${{ secrets.JIRA_EMAIL }}
22+
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
23+
run: |
24+
set -euo pipefail
25+
for v in JIRA_BASE_URL JIRA_PROJECT_KEY JIRA_ISSUE_TYPE JIRA_EMAIL JIRA_API_TOKEN; do
26+
if [ -z "${!v:-}" ]; then
27+
echo "Missing required Jira configuration: $v"
28+
exit 1
29+
fi
30+
done
31+
32+
- name: Build Jira payload from issue
33+
id: payload
34+
env:
35+
ISSUE_TITLE: ${{ github.event.issue.title }}
36+
ISSUE_BODY: ${{ github.event.issue.body }}
37+
ISSUE_URL: ${{ github.event.issue.html_url }}
38+
ISSUE_NUMBER: ${{ github.event.issue.number }}
39+
ISSUE_REPO: ${{ github.repository }}
40+
ISSUE_LABELS_JSON: ${{ toJson(github.event.issue.labels) }}
41+
JIRA_PROJECT_KEY: ${{ vars.JIRA_PROJECT_KEY }}
42+
JIRA_ISSUE_TYPE: ${{ vars.JIRA_ISSUE_TYPE }}
43+
LABEL_TO_COMPONENT_PREFIX: "component:"
44+
LABEL_TO_PRIORITY_PREFIX: "priority:"
45+
# Optional JSON map in repo variable, example:
46+
# {"label:customer-impact":{"customfield_12345":"High"}}
47+
LABEL_TO_CUSTOM_FIELDS_JSON: ${{ vars.JIRA_LABEL_TO_CUSTOM_FIELDS_JSON }}
48+
run: |
49+
set -euo pipefail
50+
51+
python3 <<'PY'
52+
import json
53+
import os
54+
from pathlib import Path
55+
56+
issue_title = os.environ["ISSUE_TITLE"]
57+
issue_body = os.environ.get("ISSUE_BODY", "") or ""
58+
issue_url = os.environ["ISSUE_URL"]
59+
issue_number = os.environ["ISSUE_NUMBER"]
60+
issue_repo = os.environ["ISSUE_REPO"]
61+
62+
labels_payload = json.loads(os.environ.get("ISSUE_LABELS_JSON", "[]") or "[]")
63+
labels = [item.get("name", "").strip() for item in labels_payload if item.get("name")]
64+
65+
component_prefix = os.environ.get("LABEL_TO_COMPONENT_PREFIX", "component:")
66+
priority_prefix = os.environ.get("LABEL_TO_PRIORITY_PREFIX", "priority:")
67+
custom_fields_map = os.environ.get("LABEL_TO_CUSTOM_FIELDS_JSON", "").strip()
68+
custom_field_overrides = json.loads(custom_fields_map) if custom_fields_map else {}
69+
70+
components = []
71+
priority = None
72+
custom_fields = {}
73+
74+
for label in labels:
75+
lower = label.lower()
76+
if lower.startswith(component_prefix.lower()):
77+
name = label[len(component_prefix):].strip()
78+
if name:
79+
components.append({"name": name})
80+
elif lower.startswith(priority_prefix.lower()):
81+
name = label[len(priority_prefix):].strip()
82+
if name:
83+
priority = {"name": name}
84+
85+
override_fields = custom_field_overrides.get(label, {})
86+
if isinstance(override_fields, dict):
87+
custom_fields.update(override_fields)
88+
89+
description = (
90+
f"GitHub issue: {issue_repo}#{issue_number}\\n"
91+
f"URL: {issue_url}\\n\\n"
92+
f"{issue_body if issue_body else '(No issue body provided)'}"
93+
)
94+
95+
fields = {
96+
"project": {"key": os.environ["JIRA_PROJECT_KEY"]},
97+
"issuetype": {"name": os.environ["JIRA_ISSUE_TYPE"]},
98+
"summary": issue_title,
99+
"description": description,
100+
"labels": labels,
101+
}
102+
if components:
103+
fields["components"] = components
104+
if priority:
105+
fields["priority"] = priority
106+
fields.update(custom_fields)
107+
108+
payload = {"fields": fields}
109+
Path("jira-payload.json").write_text(json.dumps(payload, ensure_ascii=True), encoding="utf-8")
110+
PY
111+
112+
- name: Create Jira issue via REST API
113+
id: jira
114+
env:
115+
JIRA_BASE_URL: ${{ vars.JIRA_BASE_URL }}
116+
JIRA_EMAIL: ${{ secrets.JIRA_EMAIL }}
117+
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
118+
run: |
119+
set -euo pipefail
120+
121+
JIRA_BASE_URL="${JIRA_BASE_URL%/}"
122+
case "$JIRA_BASE_URL" in
123+
*/rest/api/3) API_BASE="$JIRA_BASE_URL" ;;
124+
*) API_BASE="$JIRA_BASE_URL/rest/api/3" ;;
125+
esac
126+
127+
HTTP_CODE=$(curl -sS --retry 3 --retry-delay 2 \
128+
-u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
129+
-H "Accept: application/json" \
130+
-H "Content-Type: application/json" \
131+
-o jira-response.json \
132+
-w "%{http_code}" \
133+
-X POST \
134+
--data @jira-payload.json \
135+
"$API_BASE/issue")
136+
137+
if [ "$HTTP_CODE" != "201" ]; then
138+
echo "Jira issue creation failed. HTTP $HTTP_CODE"
139+
cat jira-response.json
140+
exit 1
141+
fi
142+
143+
JIRA_KEY="$(jq -r '.key // empty' jira-response.json)"
144+
if [ -z "$JIRA_KEY" ]; then
145+
echo "Jira response did not include issue key."
146+
cat jira-response.json
147+
exit 1
148+
fi
149+
echo "jira_key=$JIRA_KEY" >> "$GITHUB_OUTPUT"
150+
echo "jira_browse_url=${JIRA_BASE_URL}/browse/${JIRA_KEY}" >> "$GITHUB_OUTPUT"
151+
152+
- name: Comment Jira link back on the GitHub issue
153+
uses: actions/github-script@v7
154+
env:
155+
JIRA_KEY: ${{ steps.jira.outputs.jira_key }}
156+
JIRA_URL: ${{ steps.jira.outputs.jira_browse_url }}
157+
with:
158+
script: |
159+
const body = `Linked Jira ticket created: [${process.env.JIRA_KEY}](${process.env.JIRA_URL})`;
160+
await github.rest.issues.createComment({
161+
owner: context.repo.owner,
162+
repo: context.repo.repo,
163+
issue_number: context.payload.issue.number,
164+
body
165+
});

0 commit comments

Comments
 (0)