-
Notifications
You must be signed in to change notification settings - Fork 4
chore: add GitHub Action to create Jira issues on new GitHub issues #511
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This payload posts to Useful? React with 👍 / 👎.
Comment on lines
+95
to
+99
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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
AI
Feb 13, 2026
There was a problem hiding this comment.
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
AI
Feb 13, 2026
There was a problem hiding this comment.
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.
| 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
AI
Feb 13, 2026
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 👍 / 👎.
Copilot
AI
Feb 13, 2026
There was a problem hiding this comment.
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
AI
Feb 13, 2026
There was a problem hiding this comment.
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.
| uses: actions/github-script@v7 | |
| uses: actions/github-script@v8 |
There was a problem hiding this comment.
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.