chore(ci): Add workflow to sync JIRA ticket with GitHub issue#6391
chore(ci): Add workflow to sync JIRA ticket with GitHub issue#6391Yaminyam wants to merge 3 commits into
Conversation
Introduces a GitHub Actions workflow that extracts a JIRA ticket number from the PR title, fetches the corresponding GitHub issue number from JIRA, and updates the PR body with a resolves clause to link the PR to the GitHub issue. This automates cross-referencing between JIRA tickets and GitHub issues for improved traceability.
There was a problem hiding this comment.
Pull Request Overview
This PR introduces a GitHub Actions workflow that automates the synchronization between JIRA tickets and GitHub issues. When a PR is opened or synchronized, the workflow extracts the JIRA ticket number from the PR title, queries the JIRA API to retrieve the associated GitHub issue URL, and updates the PR body with a resolves clause linking to that issue.
Key changes:
- Adds automated JIRA ticket extraction from PR titles using regex pattern matching
- Implements JIRA API integration to fetch GitHub issue URLs from ticket metadata
- Automatically updates PR bodies with resolves clauses for cross-referencing
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
|
|
||
| # Extract issue number from GitHub Issue URL field | ||
| # Find field that exactly matches "GitHub Issue URL" in JIRA | ||
| GITHUB_ISSUE_URL=$(echo "$JIRA_RESPONSE" | jq -r '.fields | to_entries[] | select(.key == "GitHub Issue URL") | .value' 2>/dev/null | head -1) |
There was a problem hiding this comment.
The jq query uses .key == "GitHub Issue URL" which attempts to match field keys as literal strings, but JIRA custom field keys are typically in the format customfield_XXXXX, not human-readable names. This query will likely fail to find the field. You should either use the actual custom field ID (e.g., customfield_10001) or query by field name using .fields["GitHub Issue URL"] if the field name is mapped correctly.
| GITHUB_ISSUE_URL=$(echo "$JIRA_RESPONSE" | jq -r '.fields | to_entries[] | select(.key == "GitHub Issue URL") | .value' 2>/dev/null | head -1) | |
| # Replace customfield_12345 with your actual custom field ID for "GitHub Issue URL" | |
| GITHUB_ISSUE_URL=$(echo "$JIRA_RESPONSE" | jq -r '.fields["customfield_12345"]' 2>/dev/null) |
| echo "Generated resolves clause: $RESOLVES_CLAUSE" | ||
|
|
||
| # Replace existing resolves clause or add new one | ||
| if echo "$PR_BODY" | grep -qi "resolves #.*("; then |
There was a problem hiding this comment.
The regex pattern resolves #.*( is missing proper escaping for the parenthesis. The ( character should be escaped as \( in grep, otherwise it's treated as a literal character to match. The correct pattern should be resolves #.*\(.
| if echo "$PR_BODY" | grep -qi "resolves #.*("; then | |
| if echo "$PR_BODY" | grep -qi "resolves #.*\("; then |
| # Replace existing resolves clause or add new one | ||
| if echo "$PR_BODY" | grep -qi "resolves #.*("; then | ||
| # Replace existing resolves clause | ||
| NEW_BODY=$(echo "$PR_BODY" | sed -E "s/resolves #[0-9]+ \([^)]+\)/$RESOLVES_CLAUSE/gi") |
There was a problem hiding this comment.
The sed command uses the gi flags, but g (global) and i (case-insensitive) flags are not both available in the same sed command syntax. The correct approach in GNU sed is to use I (uppercase) for case-insensitive matching with the -E flag, so this should be s/resolves #[0-9]+ \([^)]+\)/$RESOLVES_CLAUSE/gI.
| NEW_BODY=$(echo "$PR_BODY" | sed -E "s/resolves #[0-9]+ \([^)]+\)/$RESOLVES_CLAUSE/gi") | |
| NEW_BODY=$(echo "$PR_BODY" | sed -E "s/resolves #[0-9]+ \([^)]+\)/$RESOLVES_CLAUSE/gI") |
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Extract JIRA ticket number from PR title | ||
| id: extract_ticket | ||
| run: | | ||
| # Extract JIRA ticket number from PR title (e.g., "fix(BA-1234): aaaa" -> "BA-1234") | ||
| TITLE="${{ github.event.pull_request.title }}" | ||
| echo "PR Title: $TITLE" | ||
|
|
||
| # Extract ticket number using regex pattern BA-XXXX format | ||
| TICKET_NUMBER=$(echo "$TITLE" | grep -oE 'BA-[0-9]+' | head -1) | ||
|
|
||
| if [ -n "$TICKET_NUMBER" ]; then | ||
| echo "Extracted ticket: $TICKET_NUMBER" | ||
| echo "ticket_number=$TICKET_NUMBER" >> $GITHUB_OUTPUT | ||
| echo "has_ticket=true" >> $GITHUB_OUTPUT | ||
| else | ||
| echo "No JIRA ticket number found in PR title" | ||
| echo "has_ticket=false" >> $GITHUB_OUTPUT | ||
| fi | ||
|
|
||
| - name: Get GitHub Issue URL from JIRA | ||
| if: steps.extract_ticket.outputs.has_ticket == 'true' | ||
| id: get_jira_issue | ||
| env: | ||
| JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} | ||
| JIRA_USERNAME: ${{ secrets.JIRA_USERNAME }} | ||
| JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} | ||
| run: | | ||
| TICKET_NUMBER="${{ steps.extract_ticket.outputs.ticket_number }}" | ||
| echo "Fetching GitHub Issue URL for JIRA ticket: $TICKET_NUMBER" | ||
|
|
||
| # Get ticket information through JIRA API | ||
| JIRA_RESPONSE=$(curl -s -u "$JIRA_USERNAME:$JIRA_API_TOKEN" \ | ||
| -H "Accept: application/json" \ | ||
| "$JIRA_BASE_URL/rest/api/3/issue/$TICKET_NUMBER") | ||
|
|
||
| echo "JIRA API Response: $JIRA_RESPONSE" | ||
|
|
||
| # Extract issue number from GitHub Issue URL field | ||
| # Find field that exactly matches "GitHub Issue URL" in JIRA | ||
| GITHUB_ISSUE_URL=$(echo "$JIRA_RESPONSE" | jq -r '.fields | to_entries[] | select(.key == "GitHub Issue URL") | .value' 2>/dev/null | head -1) | ||
|
|
||
| # Fail immediately if GitHub Issue URL is not found | ||
| if [ -z "$GITHUB_ISSUE_URL" ] || [ "$GITHUB_ISSUE_URL" = "null" ]; then | ||
| echo "No GitHub Issue URL found in JIRA ticket" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "Found GitHub Issue URL: $GITHUB_ISSUE_URL" | ||
| # Extract GitHub issue number (e.g., https://github.com/owner/repo/issues/123 -> 123) | ||
| GITHUB_ISSUE_NUMBER=$(echo "$GITHUB_ISSUE_URL" | grep -oE '/issues/[0-9]+' | grep -oE '[0-9]+' | head -1) | ||
|
|
||
| if [ -n "$GITHUB_ISSUE_NUMBER" ]; then | ||
| echo "Extracted GitHub Issue Number: $GITHUB_ISSUE_NUMBER" | ||
| echo "github_issue_number=$GITHUB_ISSUE_NUMBER" >> $GITHUB_OUTPUT | ||
| echo "has_github_issue=true" >> $GITHUB_OUTPUT | ||
| else | ||
| echo "❌ Could not extract issue number from GitHub Issue URL: $GITHUB_ISSUE_URL" | ||
| echo "Please verify the GitHub Issue URL format is correct." | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Update PR body with resolves clause | ||
| if: steps.extract_ticket.outputs.has_ticket == 'true' && steps.get_jira_issue.outputs.has_github_issue == 'true' | ||
| env: | ||
| TICKET_NUMBER: ${{ steps.extract_ticket.outputs.ticket_number }} | ||
| GITHUB_ISSUE_NUMBER: ${{ steps.get_jira_issue.outputs.github_issue_number }} | ||
| run: | | ||
| echo "Using GitHub issue number: $GITHUB_ISSUE_NUMBER from JIRA" | ||
|
|
||
| # Get current PR body | ||
| PR_BODY=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ | ||
| -H "Accept: application/vnd.github.v3+json" \ | ||
| "https://api.github.com/repos/${{ github.repository_owner }}/${{ github.event.repository.name }}/pulls/${{ github.event.pull_request.number }}" | \ | ||
| jq -r '.body // ""') | ||
|
|
||
| echo "Current PR body: $PR_BODY" | ||
|
|
||
| # Generate resolves clause | ||
| RESOLVES_CLAUSE="resolves #$GITHUB_ISSUE_NUMBER ($TICKET_NUMBER)" | ||
| echo "Generated resolves clause: $RESOLVES_CLAUSE" | ||
|
|
||
| # Replace existing resolves clause or add new one | ||
| if echo "$PR_BODY" | grep -qi "resolves #.*("; then | ||
| # Replace existing resolves clause | ||
| NEW_BODY=$(echo "$PR_BODY" | sed -E "s/resolves #[0-9]+ \([^)]+\)/$RESOLVES_CLAUSE/gi") | ||
| echo "Replaced existing resolves clause" | ||
| else | ||
| # Add new resolves clause | ||
| NEW_BODY=$(printf "%s\n\n%s" "$PR_BODY" "$RESOLVES_CLAUSE") | ||
| echo "Added new resolves clause" | ||
| fi | ||
|
|
||
| # Update PR body | ||
| curl -s -X PATCH \ | ||
| -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ | ||
| -H "Accept: application/vnd.github.v3+json" \ | ||
| -H "Content-Type: application/json" \ | ||
| "https://api.github.com/repos/${{ github.repository_owner }}/${{ github.event.repository.name }}/pulls/${{ github.event.pull_request.number }}" \ | ||
| -d "{\"body\": $(echo "$NEW_BODY" | jq -R -s .)}" | ||
|
|
||
| echo "✅ Added resolves clause: $RESOLVES_CLAUSE" |
Check warning
Code scanning / CodeQL
Workflow does not contain permissions Medium
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 months ago
To fix this, add an explicit permissions block so the GITHUB_TOKEN used by this workflow follows the principle of least privilege. This workflow needs to read PR metadata and update the PR body via the GitHub API, which maps to pull-requests: write. It does not need to push commits or modify repository contents, so contents can be limited to read. The cleanest fix is to add a job-level permissions block under the ticket-issue-number-sync job, above runs-on. This keeps the scope tight and avoids affecting other workflows.
Concretely, in .github/workflows/ticket-issue-number-sync.yml, under jobs:, inside the ticket-issue-number-sync: job, insert:
permissions:
contents: read
pull-requests: writeindented to match other job keys. No imports or additional methods are required, since this is purely a workflow configuration change.
| @@ -6,6 +6,9 @@ | ||
|
|
||
| jobs: | ||
| ticket-issue-number-sync: | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Checkout code |
1a10632 to
2d8c9ea
Compare
9552aac to
4af738e
Compare
Fix several issues in the ticket-issue-number-sync workflow:
1. GitHub Issue URL field detection:
- Search for any field containing GitHub issue URLs using regex
- Support customfield_* format (actual JIRA custom field format)
- Previously assumed field key would be "GitHub Issue URL" literally
2. GitHub API repository reference:
- Use github.repository instead of github.repository_owner/github.event.repository.name
- github.event.repository.name doesn't exist in the context
3. sed regex improvements:
- Fix case-insensitive matching: use [Rr]esolves instead of /gi flag
- Add optional space in regex: "resolves #[0-9]+ ?\(" to handle spacing variations
- Remove invalid sed flags
4. Better error messages:
- Add emojis (✅/❌) for clarity
- Show available JIRA fields when GitHub Issue URL not found
- More detailed format expectations in error messages
5. UX improvements:
- Add resolves clause at the beginning of PR body instead of end
- Better visibility for automated changes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Introduces a GitHub Actions workflow that extracts a JIRA ticket number from the PR title, fetches the corresponding GitHub issue number from JIRA, and updates the PR body with a resolves clause to link the PR to the GitHub issue. This automates cross-referencing between JIRA tickets and GitHub issues for improved traceability.
resolves #NNN (BA-MMM)
Checklist: (if applicable)
ai.backend.testdocsdirectory