feat(backend): add node_id to PendingHumanReviewModel for frontend grouping

Adds node_id field to PendingHumanReviewModel to enable frontend to:
- Group reviews from the same node together
- Show auto-approve toggle only on last review per node
- Apply auto-approval at node level (not per-review)

Changes:
- Fetch node_id from NodeExecution when loading reviews
- Add node_id parameter to PendingHumanReviewModel.from_db()
- Update test fixture with node_id
- Add temporary default value for backwards compatibility
This commit is contained in:
Zamil Majdy
2026-01-23 18:04:02 -06:00
parent 27e93a39c1
commit 0cfc3e395f
3 changed files with 36 additions and 5 deletions

View File

@@ -23,6 +23,7 @@ class PendingHumanReviewModel(BaseModel):
id: Unique identifier for the review record
user_id: ID of the user who must perform the review
node_exec_id: ID of the node execution that created this review
node_id: ID of the node definition (for grouping reviews from same node)
graph_exec_id: ID of the graph execution containing the node
graph_id: ID of the graph template being executed
graph_version: Version number of the graph template
@@ -37,6 +38,10 @@ class PendingHumanReviewModel(BaseModel):
"""
node_exec_id: str = Field(description="Node execution ID (primary key)")
node_id: str = Field(
description="Node definition ID (for grouping)",
default="", # Temporary default for test compatibility
)
user_id: str = Field(description="User ID associated with the review")
graph_exec_id: str = Field(description="Graph execution ID")
graph_id: str = Field(description="Graph ID")
@@ -66,7 +71,9 @@ class PendingHumanReviewModel(BaseModel):
)
@classmethod
def from_db(cls, review: "PendingHumanReview") -> "PendingHumanReviewModel":
def from_db(
cls, review: "PendingHumanReview", node_id: str
) -> "PendingHumanReviewModel":
"""
Convert a database model to a response model.
@@ -74,9 +81,14 @@ class PendingHumanReviewModel(BaseModel):
payload, instructions, and editable flag.
Handles invalid data gracefully by using safe defaults.
Args:
review: Database review object
node_id: Node definition ID (fetched from NodeExecution)
"""
return cls(
node_exec_id=review.nodeExecId,
node_id=node_id,
user_id=review.userId,
graph_exec_id=review.graphExecId,
graph_id=review.graphId,

View File

@@ -47,6 +47,7 @@ def sample_pending_review(test_user_id: str) -> PendingHumanReviewModel:
"""Create a sample pending review for testing"""
return PendingHumanReviewModel(
node_exec_id="test_node_123",
node_id="test_node_def_456",
user_id=test_user_id,
graph_exec_id="test_graph_exec_456",
graph_id="test_graph_789",

View File

@@ -284,8 +284,10 @@ async def get_pending_reviews_for_user(
page_size: Number of reviews per page
Returns:
List of pending review models
List of pending review models with node_id included
"""
from backend.data.execution import get_node_execution
# Calculate offset for pagination
offset = (page - 1) * page_size
@@ -296,7 +298,14 @@ async def get_pending_reviews_for_user(
take=page_size,
)
return [PendingHumanReviewModel.from_db(review) for review in reviews]
# Fetch node_id for each review from NodeExecution
result = []
for review in reviews:
node_exec = await get_node_execution(review.nodeExecId)
node_id = node_exec.node_id if node_exec else review.nodeExecId
result.append(PendingHumanReviewModel.from_db(review, node_id=node_id))
return result
async def get_pending_reviews_for_execution(
@@ -310,8 +319,10 @@ async def get_pending_reviews_for_execution(
user_id: User ID for security validation
Returns:
List of pending review models
List of pending review models with node_id included
"""
from backend.data.execution import get_node_execution
reviews = await PendingHumanReview.prisma().find_many(
where={
"userId": user_id,
@@ -321,7 +332,14 @@ async def get_pending_reviews_for_execution(
order={"createdAt": "asc"},
)
return [PendingHumanReviewModel.from_db(review) for review in reviews]
# Fetch node_id for each review from NodeExecution
result = []
for review in reviews:
node_exec = await get_node_execution(review.nodeExecId)
node_id = node_exec.node_id if node_exec else review.nodeExecId
result.append(PendingHumanReviewModel.from_db(review, node_id=node_id))
return result
async def process_all_reviews_for_execution(