-
Notifications
You must be signed in to change notification settings - Fork 5
feat: Jira status transition #56
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
base: main
Are you sure you want to change the base?
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -46,6 +46,10 @@ interface HandleJiraArg { | |||||||||||||||||||||||||||||||||||||||||||||||||||
| user?: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| login?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| draft?: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| state?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| merged?: boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| mergeable_state?: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| requestedBy: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| commentId: number; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -120,7 +124,7 @@ export const handleJira = async ({ context, boardName, parentTaskKey, pr, reques | |||||||||||||||||||||||||||||||||||||||||||||||||||
| fields: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| project: { key: projectKey }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...(isSubtask ? { parent: { key: parentTaskKey } } : {}), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| summary: `[PR #${pr.number}] ${pr.title}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| summary: `${pr.title} [PR #${pr.number}]`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| issuetype: { name: isSubtask ? 'Sub-task' : 'Task' }, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...(hasCommunityLabel ? { labels: ['community'] } : {}), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ...(useFixVersions && milestoneName ? { fixVersions: [{ name: milestoneName }] } : {}), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -183,6 +187,55 @@ export const handleJira = async ({ context, boardName, parentTaskKey, pr, reques | |||||||||||||||||||||||||||||||||||||||||||||||||||
| body: `${pr.body?.trim() || 'no description'} \n\n Task: [${task.key}]`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function updateJiraStatus(issueKey: string, targetStatus: string) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const transitionsRes = await jiraFetch(jiraBaseUrl, jiraApiToken, `/rest/api/3/issue/${issueKey}/transitions`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!transitionsRes.ok) throw new Error('Failed to fetch Jira transitions'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const transitionsData = (await transitionsRes.json()) as { transitions: { id: string; to: { name: string } }[] }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const transitions = transitionsData.transitions; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const transition = transitions.find((t) => t.to.name.toLowerCase() === targetStatus.toLowerCase()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!transition) throw new Error(`No Jira transition found for status: ${targetStatus}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const transitionRes = await jiraFetch(jiraBaseUrl, jiraApiToken, `/rest/api/3/issue/${issueKey}/transitions`, { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| method: 'POST', | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| body: JSON.stringify({ transition: { id: transition.id } }), | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!transitionRes.ok) throw new Error(`Failed to transition Jira issue to ${targetStatus}`); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+192
to
+202
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| let jiraTargetStatus: string | null = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (pr.merged) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| jiraTargetStatus = 'Done'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (pr.mergeable_state && pr.mergeable_state.toLowerCase().includes('queue')) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| jiraTargetStatus = 'QA Tested'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (pr.state === 'open' && pr.draft) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| jiraTargetStatus = 'In Progress'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } else if (pr.state === 'open') { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Check for approval status | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { owner, repo } = context.repo(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+206
to
+214
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (pr.merged) { | |
| jiraTargetStatus = 'Done'; | |
| } else if (pr.mergeable_state && pr.mergeable_state.toLowerCase().includes('queue')) { | |
| jiraTargetStatus = 'QA Tested'; | |
| } else if (pr.state === 'open' && pr.draft) { | |
| jiraTargetStatus = 'In Progress'; | |
| } else if (pr.state === 'open') { | |
| // Check for approval status | |
| const { owner, repo } = context.repo(); | |
| // Fetch full PR details to ensure we have accurate status fields | |
| const { owner, repo } = context.repo(); | |
| const pullRequest = await context.octokit.pulls.get({ | |
| owner, | |
| repo, | |
| pull_number: pr.number, | |
| }); | |
| if (pullRequest.data.merged) { | |
| jiraTargetStatus = 'Done'; | |
| } else if (pullRequest.data.mergeable_state && pullRequest.data.mergeable_state.toLowerCase().includes('queue')) { | |
| jiraTargetStatus = 'QA Tested'; | |
| } else if (pullRequest.data.state === 'open' && pullRequest.data.draft) { | |
| jiraTargetStatus = 'In Progress'; | |
| } else if (pullRequest.data.state === 'open') { | |
| // Check for approval status |
Copilot
AI
Mar 10, 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.
pulls.listReviews is paginated (defaults to 30). On PRs with many reviews, an older approval could be outside the first page and the status decision would be wrong. Use octokit.paginate (or request per_page: 100 and paginate) to ensure you consider all reviews, or avoid pagination entirely by using GraphQL reviewDecision.
| const reviews = await context.octokit.pulls.listReviews({ | |
| owner, | |
| repo, | |
| pull_number: pr.number, | |
| }); | |
| const approved = reviews.data.some((review: { state: string }) => review.state === 'APPROVED'); | |
| const reviews = await context.octokit.paginate( | |
| context.octokit.pulls.listReviews, | |
| { | |
| owner, | |
| repo, | |
| pull_number: pr.number, | |
| per_page: 100, | |
| }, | |
| ); | |
| const approved = reviews.some((review: { state: string }) => review.state === 'APPROVED'); |
Copilot
AI
Mar 10, 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.
reviews.data.some(review.state === 'APPROVED') can report approved even when a later review requested changes (or the approval was dismissed), because the API returns a history of reviews. This can transition Jira to "QA Tested" incorrectly. Prefer using GraphQL reviewDecision (APPROVED/CHANGES_REQUESTED/REVIEW_REQUIRED) or computing the latest review state per reviewer before deciding.
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 transitions endpoints interpolate
issueKeydirectly into the URL. To avoid path issues (and to be consistent with the existingprojectHasVersioncall),issueKeyshould be wrapped withencodeURIComponentwhen building/rest/api/3/issue/${...}/transitions.