From 1c3bc9e4b23221cdfad397d760b10b7e4bd50656 Mon Sep 17 00:00:00 2001 From: Kent Yao Date: Mon, 29 Dec 2025 15:09:00 +0800 Subject: [PATCH 1/3] [SPARK-54860][SPARK-999999][INFRA] Add JIRA Ticket Validating in GHA ### What changes were proposed in this pull request? This PR adds a new GitHub Action workflow that automatically validates pull request titles and extracts JIRA ticket information. ### Why are the changes needed? To improve contributor experience and streamline the review process by automatically displaying JIRA context in PRs. ### Does this PR introduce _any_ user-facing change? No. This is an infrastructure change that only affects the GitHub PR workflow. ### How was this patch tested? Tested locally using test-jira-action.py script with various PR title formats and JIRA API calls. ### Was this patch authored or co-authored using generative AI tooling? Copilot w/ Claude Sonnet 4.5 --- .github/workflows/labeler.yml | 154 +++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index ccfdeb1653b9b..c586232b90652 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -24,7 +24,9 @@ # See also https://github.community/t/specify-check-suite-when-creating-a-checkrun/118380/10 name: "On pull requests" -on: pull_request_target +on: + pull_request_target: + types: [opened, edited, reopened] jobs: label: @@ -38,3 +40,153 @@ jobs: with: repo-token: "${{ secrets.GITHUB_TOKEN }}" sync-labels: true + + jira-info: + name: Comment JIRA information + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Extract JIRA IDs and comment + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const prTitle = context.payload.pull_request.title; + const prNumber = context.payload.pull_request.number; + + // Extract JIRA IDs from PR title + const jiraIdRegex = /SPARK-\d+/g; + const jiraIds = prTitle.match(jiraIdRegex); + + // If no JIRA IDs found, check for [MINOR] tag + if (!jiraIds || jiraIds.length === 0) { + const minorRegex = /^\[MINOR\]/i; + if (minorRegex.test(prTitle)) { + console.log('PR title has [MINOR] tag, skipping'); + return; + } + + // Post reminder comment + const reminderComment = `## ⚠️ Pull Request Title Validation\n\nThis pull request title does not contain a JIRA issue ID.\n\nPlease update the title to either:\n- Include a JIRA ID: \`[SPARK-12345] Your description\`\n- Mark as minor change: \`[MINOR] Your description\`\n\nFor minor changes that don't require a JIRA ticket (e.g., typo fixes), please prefix the title with \`[MINOR]\`.\n\n---\n*This comment was automatically generated by GitHub Actions*`; + + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber + }); + + const botComment = comments.data.find(comment => + comment.user.type === 'Bot' && + (comment.body.includes('## JIRA Issue Information') || comment.body.includes('## ⚠️ Pull Request Title Validation')) + ); + + if (botComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: reminderComment + }); + console.log('Updated reminder comment'); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: reminderComment + }); + console.log('Created reminder comment'); + } + return; + } + + // Remove duplicates + const uniqueJiraIds = [...new Set(jiraIds)]; + console.log(`Found JIRA IDs: ${uniqueJiraIds.join(', ')}`); + + // Fetch JIRA information for each ID + const jiraBaseUrl = 'https://issues.apache.org/jira'; + const jiraInfos = []; + + for (const jiraId of uniqueJiraIds) { + try { + const response = await fetch(`${jiraBaseUrl}/rest/api/2/issue/${jiraId}`); + + if (!response.ok) { + jiraInfos.push({ + id: jiraId, + type: 'Unknown', + error: `Failed to fetch (HTTP ${response.status})` + }); + continue; + } + + const data = await response.json(); + const fields = data.fields; + + jiraInfos.push({ + id: jiraId, + type: fields.issuetype?.name || 'Unknown', + summary: fields.summary || 'N/A', + assignee: fields.assignee ? fields.assignee.displayName : 'None', + status: fields.status ? fields.status.name : 'Unknown', + affected: fields.versions ? fields.versions.map(v => v.name) : [] + }); + } catch (error) { + console.error(`Error fetching ${jiraId}:`, error); + jiraInfos.push({ + id: jiraId, + type: 'Unknown', + error: error.message + }); + } + } + + // Format comment + let commentBody = '## JIRA Issue Information\n\n'; + + for (const info of jiraInfos) { + if (info.error) { + commentBody += `=== ${info.type} ${info.id} ===\n`; + commentBody += `Error: ${info.error}\n\n`; + } else { + commentBody += `=== ${info.type} ${info.id} ===\n`; + commentBody += `Summary ${info.summary}\n`; + commentBody += `Assignee ${info.assignee}\n`; + commentBody += `Status ${info.status}\n`; + commentBody += `Affected ${JSON.stringify(info.affected)}\n\n`; + } + } + + commentBody += '---\n*This comment was automatically generated by GitHub Actions*'; + + // Check if there's an existing comment from this action + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber + }); + + const botComment = comments.data.find(comment => + comment.user.type === 'Bot' && + (comment.body.includes('## JIRA Issue Information') || comment.body.includes('## ⚠️ Pull Request Title Validation')) + ); + + if (botComment) { + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: commentBody + }); + console.log('Updated existing comment'); + } else { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: commentBody + }); + console.log('Created new comment'); + } From 7ba4db8f22f4052b296e0d308fd846b1ba233865 Mon Sep 17 00:00:00 2001 From: Kent Yao Date: Mon, 29 Dec 2025 16:24:27 +0800 Subject: [PATCH 2/3] Update .github/workflows/labeler.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/labeler.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index c586232b90652..a100ca6f56d43 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -152,10 +152,10 @@ jobs: commentBody += `Error: ${info.error}\n\n`; } else { commentBody += `=== ${info.type} ${info.id} ===\n`; - commentBody += `Summary ${info.summary}\n`; - commentBody += `Assignee ${info.assignee}\n`; - commentBody += `Status ${info.status}\n`; - commentBody += `Affected ${JSON.stringify(info.affected)}\n\n`; + commentBody += `Summary: ${info.summary}\n`; + commentBody += `Assignee: ${info.assignee}\n`; + commentBody += `Status: ${info.status}\n`; + commentBody += `Affected: ${JSON.stringify(info.affected)}\n\n`; } } From ad0673d4c16d121a20bd16ea7a9ec1d8183de30c Mon Sep 17 00:00:00 2001 From: Kent Yao Date: Mon, 29 Dec 2025 21:19:10 +0800 Subject: [PATCH 3/3] Update .github/workflows/labeler.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/labeler.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index a100ca6f56d43..77b1e733d5e7a 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -56,7 +56,7 @@ jobs: const prNumber = context.payload.pull_request.number; // Extract JIRA IDs from PR title - const jiraIdRegex = /SPARK-\d+/g; + const jiraIdRegex = /\bSPARK-\d+\b/g; const jiraIds = prTitle.match(jiraIdRegex); // If no JIRA IDs found, check for [MINOR] tag