Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 165 additions & 0 deletions .github/workflows/jira_issue_on_open.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
name: Create Jira ticket on GitHub issue open

on:
issues:
types: [opened]

permissions:
contents: read
issues: write

jobs:
create-jira-ticket:
runs-on: ubuntu-latest
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The job does not specify a timeout-minutes value. The existing jira_codex_pr.yml workflow sets timeout-minutes: 60. Consider adding a timeout to prevent the job from running indefinitely if there are issues with the Jira API or network connectivity. A reasonable timeout for this workflow would be 5-10 minutes.

Suggested change
runs-on: ubuntu-latest
runs-on: ubuntu-latest
timeout-minutes: 10

Copilot uses AI. Check for mistakes.
environment: jira-codex
steps:
- name: Validate Jira configuration
env:
JIRA_BASE_URL: ${{ vars.JIRA_BASE_URL }}
JIRA_PROJECT_KEY: ${{ vars.JIRA_PROJECT_KEY }}
JIRA_ISSUE_TYPE: ${{ vars.JIRA_ISSUE_TYPE }}
JIRA_EMAIL: ${{ secrets.JIRA_EMAIL }}
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
run: |
set -euo pipefail
for v in JIRA_BASE_URL JIRA_PROJECT_KEY JIRA_ISSUE_TYPE JIRA_EMAIL JIRA_API_TOKEN; do
if [ -z "${!v:-}" ]; then
echo "Missing required Jira configuration: $v"
exit 1
fi
done

- name: Build Jira payload from issue
id: payload
env:
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY: ${{ github.event.issue.body }}
ISSUE_URL: ${{ github.event.issue.html_url }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
ISSUE_REPO: ${{ github.repository }}
ISSUE_LABELS_JSON: ${{ toJson(github.event.issue.labels) }}
JIRA_PROJECT_KEY: ${{ vars.JIRA_PROJECT_KEY }}
JIRA_ISSUE_TYPE: ${{ vars.JIRA_ISSUE_TYPE }}
LABEL_TO_COMPONENT_PREFIX: "component:"
LABEL_TO_PRIORITY_PREFIX: "priority:"
# Optional JSON map in repo variable, example:
# {"label:customer-impact":{"customfield_12345":"High"}}
LABEL_TO_CUSTOM_FIELDS_JSON: ${{ vars.JIRA_LABEL_TO_CUSTOM_FIELDS_JSON }}
run: |
set -euo pipefail

python3 <<'PY'
import json
import os
from pathlib import Path

issue_title = os.environ["ISSUE_TITLE"]
issue_body = os.environ.get("ISSUE_BODY", "") or ""
issue_url = os.environ["ISSUE_URL"]
issue_number = os.environ["ISSUE_NUMBER"]
issue_repo = os.environ["ISSUE_REPO"]

labels_payload = json.loads(os.environ.get("ISSUE_LABELS_JSON", "[]") or "[]")
labels = [item.get("name", "").strip() for item in labels_payload if item.get("name")]

component_prefix = os.environ.get("LABEL_TO_COMPONENT_PREFIX", "component:")
priority_prefix = os.environ.get("LABEL_TO_PRIORITY_PREFIX", "priority:")
custom_fields_map = os.environ.get("LABEL_TO_CUSTOM_FIELDS_JSON", "").strip()
custom_field_overrides = json.loads(custom_fields_map) if custom_fields_map else {}

components = []
priority = None
custom_fields = {}

for label in labels:
lower = label.lower()
if lower.startswith(component_prefix.lower()):
name = label[len(component_prefix):].strip()
if name:
components.append({"name": name})
elif lower.startswith(priority_prefix.lower()):
name = label[len(priority_prefix):].strip()
if name:
priority = {"name": name}

override_fields = custom_field_overrides.get(label, {})
if isinstance(override_fields, dict):
custom_fields.update(override_fields)

description = (
f"GitHub issue: {issue_repo}#{issue_number}\\n"
f"URL: {issue_url}\\n\\n"
f"{issue_body if issue_body else '(No issue body provided)'}"
)

fields = {
"project": {"key": os.environ["JIRA_PROJECT_KEY"]},
"issuetype": {"name": os.environ["JIRA_ISSUE_TYPE"]},
"summary": issue_title,
"description": description,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Send Jira description in ADF for REST API v3

This payload posts to /rest/api/3/issue but sets fields.description to a plain string, which causes issue creation to fail on Jira Cloud instances that require Atlassian Document Format for v3 rich-text fields (the API returns 400 for invalid description shape). Build description as an ADF object (or use v2 if you want plain text) so new GitHub issues consistently create Jira tickets.

Useful? React with 👍 / 👎.

Comment on lines +95 to +99
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Jira Cloud REST API v3 expects the 'description' field to be in Atlassian Document Format (ADF), not plain text. The current implementation sends a plain text string, which will likely cause the API request to fail with a 400 error. The description should be formatted as ADF JSON. For example: {"type": "doc", "version": 1, "content": [{"type": "paragraph", "content": [{"type": "text", "text": "your text here"}]}]}.

Suggested change
fields = {
"project": {"key": os.environ["JIRA_PROJECT_KEY"]},
"issuetype": {"name": os.environ["JIRA_ISSUE_TYPE"]},
"summary": issue_title,
"description": description,
adf_description = {
"type": "doc",
"version": 1,
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": description,
}
],
}
],
}
fields = {
"project": {"key": os.environ["JIRA_PROJECT_KEY"]},
"issuetype": {"name": os.environ["JIRA_ISSUE_TYPE"]},
"summary": issue_title,
"description": adf_description,

Copilot uses AI. Check for mistakes.
"labels": labels,
}
if components:
fields["components"] = components
if priority:
fields["priority"] = priority
fields.update(custom_fields)

payload = {"fields": fields}
Path("jira-payload.json").write_text(json.dumps(payload, ensure_ascii=True), encoding="utf-8")
PY

- name: Create Jira issue via REST API
id: jira
env:
JIRA_BASE_URL: ${{ vars.JIRA_BASE_URL }}
JIRA_EMAIL: ${{ secrets.JIRA_EMAIL }}
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
run: |
set -euo pipefail

JIRA_BASE_URL="${JIRA_BASE_URL%/}"
case "$JIRA_BASE_URL" in
*/rest/api/3) API_BASE="$JIRA_BASE_URL" ;;
*) API_BASE="$JIRA_BASE_URL/rest/api/3" ;;
esac
Comment on lines +121 to +125
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JIRA_BASE_URL is not validated to ensure it starts with http:// or https:// before being used. The existing jira_codex_pr.yml workflow includes validation to ensure the URL has a proper scheme. Consider adding the same validation here to prevent configuration errors.

Copilot uses AI. Check for mistakes.

HTTP_CODE=$(curl -sS --retry 3 --retry-delay 2 \
-u "$JIRA_EMAIL:$JIRA_API_TOKEN" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-o jira-response.json \
-w "%{http_code}" \
-X POST \
--data @jira-payload.json \
"$API_BASE/issue")

if [ "$HTTP_CODE" != "201" ]; then
echo "Jira issue creation failed. HTTP $HTTP_CODE"
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the Jira API call fails, the error message shows the HTTP code and response body, but it doesn't include the request URL or payload that was sent. This makes debugging more difficult. Consider logging the API endpoint URL (without credentials) to help troubleshoot configuration issues.

Suggested change
echo "Jira issue creation failed. HTTP $HTTP_CODE"
echo "Jira issue creation failed. HTTP $HTTP_CODE"
echo "Request URL: $API_BASE/issue"
echo "Request payload:"
cat jira-payload.json
echo "Response body:"

Copilot uses AI. Check for mistakes.
cat jira-response.json
exit 1
fi

JIRA_KEY="$(jq -r '.key // empty' jira-response.json)"
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow uses jq to parse the Jira API response (line 143) but doesn't ensure jq is installed. The existing jira_codex_pr.yml workflow includes a step to ensure jq is available. Consider adding the same check here or verifying that jq is pre-installed on ubuntu-latest runners.

Copilot uses AI. Check for mistakes.
if [ -z "$JIRA_KEY" ]; then
echo "Jira response did not include issue key."
cat jira-response.json
exit 1
fi
echo "jira_key=$JIRA_KEY" >> "$GITHUB_OUTPUT"
echo "jira_browse_url=${JIRA_BASE_URL}/browse/${JIRA_KEY}" >> "$GITHUB_OUTPUT"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Derive browse URL from site base, not API base

The step explicitly supports JIRA_BASE_URL values that already end with /rest/api/3, but jira_browse_url is built directly from JIRA_BASE_URL; in that valid configuration the posted link becomes .../rest/api/3/browse/<KEY>, which does not resolve to the Jira issue page. Normalize to the site root before composing the browse link.

Useful? React with 👍 / 👎.

Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The jira_browse_url uses JIRA_BASE_URL which may include '/rest/api/3' suffix based on the normalization logic in lines 121-125. This would result in an incorrect browse URL like 'https://domain.atlassian.net/rest/api/3/browse/KEY-123' instead of 'https://domain.atlassian.net/browse/KEY-123'. The browse URL should be constructed from the original base URL before the '/rest/api/3' suffix is added.

Copilot uses AI. Check for mistakes.

- name: Comment Jira link back on the GitHub issue
uses: actions/github-script@v7
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow uses actions/github-script@v7, but the existing dependabot_automerge.yml workflow uses actions/github-script@v8. For consistency and to use the latest version, consider updating to v8.

Suggested change
uses: actions/github-script@v7
uses: actions/github-script@v8

Copilot uses AI. Check for mistakes.
env:
JIRA_KEY: ${{ steps.jira.outputs.jira_key }}
JIRA_URL: ${{ steps.jira.outputs.jira_browse_url }}
with:
script: |
const body = `Linked Jira ticket created: [${process.env.JIRA_KEY}](${process.env.JIRA_URL})`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body
});
Loading