Skip to content

Derive PR lifecycle for Jira from webhook action (flat topics)#444

Merged
Bala-Sakabattula merged 5 commits intorelease-engineering:mainfrom
Bala-Sakabattula:pr-transition-fix
Mar 30, 2026
Merged

Derive PR lifecycle for Jira from webhook action (flat topics)#444
Bala-Sakabattula merged 5 commits intorelease-engineering:mainfrom
Bala-Sakabattula:pr-transition-fix

Conversation

@Bala-Sakabattula
Copy link
Copy Markdown
Collaborator

Fedora Messaging / webhook-2fm now uses a flat PR topic (github.pull_request) where the topic suffix no longer encodes closed / reopened. This change aligns PR.from_github with the GitHub payload: lifecycle is taken from body["action"], and for closed from pull_request["merged"] (merged vs closed without merge).

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Derive PR lifecycle from webhook action for flat topics

✨ Enhancement 🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Derive PR lifecycle from webhook action instead of topic suffix
• Support flat topic format (github.pull_request) with action parameter
• Distinguish merged vs closed PRs using pr["merged"] field
• Normalize unknown actions to "open" state for robustness
Diagram
flowchart LR
  webhook["Webhook payload<br/>with action field"]
  action["action parameter<br/>opened/closed/reopened/edited"]
  merged["pr.merged field<br/>for closed PRs"]
  suffix["Determine suffix<br/>open/merged/closed/reopened"]
  pr_obj["PR intermediary<br/>object created"]
  
  webhook -- "extract" --> action
  webhook -- "extract" --> merged
  action -- "map to suffix" --> suffix
  merged -- "distinguish closed" --> suffix
  suffix --> pr_obj
Loading

Grey Divider

File Changes

1. sync2jira/intermediary.py ✨ Enhancement +14/-9

Implement action-based PR lifecycle detection

• Added optional action parameter to from_github() method
• Replaced suffix-based lifecycle detection with action-based logic
• Implemented lifecycle mapping: opened→open, closed→merged/closed, reopened→reopened
• Added fallback to "open" for unknown actions or missing action parameter

sync2jira/intermediary.py


2. sync2jira/upstream_pr.py ✨ Enhancement +2/-2

Pass webhook action to PR creation methods

• Pass webhook action field to PR.from_github() in handle_github_message()
• Explicitly pass action=None in github_prs() for non-webhook PR retrieval
• Maintains backward compatibility with existing API calls

sync2jira/upstream_pr.py


3. tests/test_intermediary.py 🧪 Tests +31/-6

Add comprehensive PR lifecycle action tests

• Updated test_from_github_pr_reopen() to use flat topic and action parameter
• Added comprehensive test_from_github_pr_flat_topic_normalizes_suffix() test
• Tests all action types: opened, closed (merged/unmerged), reopened, edited
• Validates fallback behavior for missing or unknown actions

tests/test_intermediary.py


View more (1)
4. tests/test_upstream_pr.py 🧪 Tests +2/-0

Update PR creation test assertions

• Updated mock assertions to include action parameter in PR.from_github() calls
• Added action=None to both webhook and non-webhook test cases
• Ensures test expectations match new method signature

tests/test_upstream_pr.py


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review bot commented Mar 24, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Old PR topics mis-mapped 🐞 Bug ✓ Correctness
Description
PR.from_github now normalizes any non-lifecycle suffix to "open" when action is missing, so old
github2fedmsg topics like github.pull_request.closed / github.pull_request.reopened will be
treated as open. This breaks downstream PR comment wording and merge/close/reopen transitions which
depend on pr.suffix containing "closed"/"reopened"/"merged".
Code

sync2jira/intermediary.py[R218-231]

+        _lifecycle = frozenset({"open", "merged", "closed", "reopened"})
+        if action:
+            if action == "reopened":
+                suffix = "reopened"
+            elif action == "closed":
+                suffix = "merged" if pr.get("merged") else "closed"
+            elif action == "opened":
+                suffix = "open"
       else:
-                suffix = "closed"
+                suffix = "open"
+        elif suffix in _lifecycle:
+            pass
+        else:
+            suffix = "open"
Evidence
sync2jira still registers and routes old-style PR topics (e.g., github.pull_request.closed,
github.pull_request.reopened) but callback() derives suffix from the topic tail, meaning
old-style suffixes are full strings like github.pull_request.closed. With action=None (as in
tests and potentially legacy message bodies), the new from_github logic only preserves suffixes
exactly equal to one of {open, merged, closed, reopened}; otherwise it forces suffix = "open",
unlike the previous substring matching that handled ...closed/...reopened. Downstream PR
handling checks for substrings (e.g., "closed" in pr_suffix, "merged" in pr.suffix) and will now
behave as if the PR was merely opened/mentioned.

sync2jira/intermediary.py[218-231]
sync2jira/main.py[79-90]
sync2jira/main.py[146-153]
sync2jira/upstream_pr.py[30-51]
sync2jira/downstream_pr.py[54-65]
sync2jira/downstream_pr.py[128-131]
tests/test_upstream_pr.py[43-56]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`sync2jira.intermediary.PR.from_github()` now derives lifecycle only from the webhook `action` when present, otherwise it only accepts exact lifecycle tokens (`open|closed|merged|reopened`) and defaults everything else to `open`. This breaks backward compatibility for old github2fedmsg topics (e.g. `github.pull_request.closed`) when `action` is missing.
### Issue Context
The codebase still registers old PR topics in `pr_handlers`, and `callback()` derives `suffix` from the topic tail. Old-style suffixes are not equal to lifecycle tokens, but contain them as substrings.
### Fix Focus Areas
- sync2jira/intermediary.py[204-232]
- When `action` is falsy, fall back to substring parsing similar to the previous behavior:
- if `"reopened" in suffix`: set `suffix="reopened"`
- elif `"closed" in suffix`: set `suffix="merged" if pr.get("merged") else "closed"`
- elif `"opened" in suffix`: set `suffix="open"` (optional)
- else: default to `"open"`
- Keep accepting exact lifecycle tokens as-is.
- (Optional) tests/test_intermediary.py
- Add coverage for old topic suffixes without `action` (e.g. `suffix="github.pull_request.closed"`, no `action`, expect `closed/merged`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

webbnh

This comment was marked as resolved.

webbnh

This comment was marked as resolved.

Copy link
Copy Markdown
Collaborator

@webbnh webbnh left a comment

Choose a reason for hiding this comment

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

👍

@Bala-Sakabattula Bala-Sakabattula merged commit a5cbc78 into release-engineering:main Mar 30, 2026
6 checks passed
@Bala-Sakabattula Bala-Sakabattula deleted the pr-transition-fix branch March 30, 2026 19:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants