Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/forge/workflow/task_takeover/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

from langgraph.graph import END, StateGraph

from forge.integrations.jira.client import JiraClient
from forge.models.workflow import ForgeLabel, JiraStatus
from forge.workflow.gates.task_plan_approval import (
route_task_plan_approval,
task_plan_approval_gate,
Expand Down Expand Up @@ -209,7 +211,21 @@ def _route_human_review_task_takeover(state: TaskTakeoverState) -> str:


async def complete_task_takeover(state: TaskTakeoverState) -> TaskTakeoverState:
"""Mark Task Takeover workflow complete after PR merge."""
"""Mark Task Takeover workflow complete after PR merge and transition Jira ticket."""
ticket_key = state["ticket_key"]
logger.info(f"Completing task takeover workflow for ticket: {ticket_key}")

jira = JiraClient()
try:
try:
await jira.transition_issue(ticket_key, JiraStatus.CLOSED.value)
await jira.set_workflow_label(ticket_key, ForgeLabel.TASK_REVIEW_APPROVED)
logger.info(f"Task {ticket_key} successfully transitioned to Closed/Done")
except Exception as e:
logger.warning(f"Failed to transition Jira status/label for {ticket_key}: {e}")
finally:
await jira.close()

return update_state_timestamp(
{
**state,
Expand Down
45 changes: 42 additions & 3 deletions tests/workflow/test_task_takeover_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import pytest
from langgraph.graph import END, StateGraph

from forge.models.workflow import ForgeLabel, TicketType
from forge.models.workflow import ForgeLabel, JiraStatus, TicketType
from forge.workflow.gates.task_plan_approval import route_task_plan_approval
from forge.workflow.task_takeover.graph import (
_route_after_answer,
Expand Down Expand Up @@ -241,13 +241,52 @@ def test_human_review_approved_routes_to_task_takeover_complete(self) -> None:
assert _route_human_review_task_takeover(state) == "complete_task_takeover"

@pytest.mark.asyncio
async def test_complete_task_takeover_marks_workflow_complete(self) -> None:
state = make_task_state(current_node="human_review_gate", is_paused=True)
@patch("forge.workflow.task_takeover.graph.JiraClient")
async def test_complete_task_takeover_marks_workflow_complete(
self, mock_jira_class: MagicMock
) -> None:
mock_jira = MagicMock()
mock_jira.transition_issue = AsyncMock()
mock_jira.set_workflow_label = AsyncMock()
mock_jira.close = AsyncMock()
mock_jira_class.return_value = mock_jira

state = make_task_state(
ticket_key="TASK-123", current_node="human_review_gate", is_paused=True
)
result = await complete_task_takeover(state)
assert result["current_node"] == "complete"
assert result["is_paused"] is False
assert result["ci_fix_attempt"] == 0

mock_jira.transition_issue.assert_called_once_with("TASK-123", JiraStatus.CLOSED.value)
mock_jira.set_workflow_label.assert_called_once_with(
"TASK-123", ForgeLabel.TASK_REVIEW_APPROVED
)
mock_jira.close.assert_called_once()

@pytest.mark.asyncio
@patch("forge.workflow.task_takeover.graph.JiraClient")
async def test_complete_task_takeover_resilience_on_exception(
self, mock_jira_class: MagicMock
) -> None:
mock_jira = MagicMock()
mock_jira.transition_issue = AsyncMock(side_effect=Exception("Jira is down"))
mock_jira.set_workflow_label = AsyncMock()
mock_jira.close = AsyncMock()
mock_jira_class.return_value = mock_jira

state = make_task_state(
ticket_key="TASK-123", current_node="human_review_gate", is_paused=True
)
result = await complete_task_takeover(state)
assert result["current_node"] == "complete"
assert result["is_paused"] is False
assert result["ci_fix_attempt"] == 0

mock_jira.transition_issue.assert_called_once_with("TASK-123", JiraStatus.CLOSED.value)
mock_jira.close.assert_called_once()


class TestInteractiveGateBehavior:
"""Test interactive gate behavior for plan approvals, questions, and revision requests."""
Expand Down
Loading