@@ -76,15 +76,18 @@ func FormatChannelMessageBase(ctx context.Context, params MessageParams) string
7676 } else if pr .State == "closed" {
7777 emoji , state = ":x:" , "?st=closed"
7878 slog .Info ("using :x: emoji - PR is closed but not merged" , "pr" , prID )
79- } else if a .WorkflowState == "newly_published" {
80- emoji , state = ":new:" , "?st=newly_published"
81- slog .Info ("using :new: emoji - newly published PR" , "pr" , prID , "workflow_state" , a .WorkflowState )
79+ } else if a .WorkflowState != "" {
80+ // Use WorkflowState as primary signal (most reliable source of truth)
81+ emoji , state = emojiFromWorkflowState (a .WorkflowState , a .NextAction )
82+ slog .Info ("using emoji from workflow_state" , "pr" , prID , "workflow_state" , a .WorkflowState , "emoji" , emoji , "state_param" , state )
8283 } else if len (a .NextAction ) > 0 {
84+ // Fallback to NextAction if no WorkflowState (shouldn't normally happen)
8385 action := PrimaryAction (a .NextAction )
8486 emoji = PrefixForAction (action )
8587 state = stateParam (params .CheckResult )
86- slog .Info ("using emoji from primary next_action" , "pr" , prID , "primary_action" , action , "emoji" , emoji , "state_param" , state )
88+ slog .Info ("using emoji from primary next_action (no workflow_state) " , "pr" , prID , "primary_action" , action , "emoji" , emoji , "state_param" , state )
8789 } else {
90+ // Final fallback based on PR properties
8891 emoji , state = fallbackEmoji (params .CheckResult )
8992 //nolint:revive // line length acceptable for structured logging
9093 slog .Info ("using fallback emoji - no workflow_state or next_actions" , "pr" , prID , "emoji" , emoji , "state_param" , state , "fallback_reason" , "empty_workflow_state_and_next_actions" )
@@ -114,6 +117,62 @@ func FormatNextActionsSuffix(ctx context.Context, params MessageParams) string {
114117 return ""
115118}
116119
120+ // emojiFromWorkflowState determines emoji based on WorkflowState as the primary signal.
121+ // Uses NextAction for additional granularity in specific states (e.g., test failures).
122+ func emojiFromWorkflowState (workflowState string , nextActions map [string ]turn.Action ) (emoji , state string ) {
123+ switch workflowState {
124+ case string (turn .StateNewlyPublished ):
125+ return ":new:" , "?st=newly_published"
126+
127+ case string (turn .StateInDraft ):
128+ return ":construction:" , "?st=draft"
129+
130+ case string (turn .StatePublishedWaitingForTests ):
131+ // Use NextAction to distinguish between broken tests vs pending tests
132+ if len (nextActions ) > 0 {
133+ action := PrimaryAction (nextActions )
134+ if action == string (turn .ActionFixTests ) {
135+ return ":cockroach:" , "?st=tests_broken"
136+ }
137+ if action == string (turn .ActionTestsPending ) {
138+ return ":test_tube:" , "?st=tests_running"
139+ }
140+ }
141+ // Default to tests running
142+ return ":test_tube:" , "?st=tests_running"
143+
144+ case string (turn .StateTestedWaitingForAssignment ):
145+ // Waiting for reviewers to be assigned
146+ return ":shrug:" , "?st=awaiting_assignment"
147+
148+ case string (turn .StateAssignedWaitingForReview ):
149+ // Waiting for assigned reviewers to review
150+ return ":hourglass:" , "?st=awaiting_review"
151+
152+ case string (turn .StateReviewedNeedsRefinement ):
153+ // Author needs to address feedback/comments
154+ return ":carpentry_saw:" , "?st=changes_requested"
155+
156+ case string (turn .StateRefinedWaitingForApproval ):
157+ // Waiting for final approval after refinements
158+ return ":hourglass:" , "?st=awaiting_approval"
159+
160+ case string (turn .StateApprovedWaitingForMerge ):
161+ // Approved and ready to merge
162+ return ":white_check_mark:" , "?st=approved"
163+
164+ default :
165+ // Unknown workflow state - fall back to NextAction
166+ slog .Warn ("unknown workflow state, falling back to NextAction" ,
167+ "workflow_state" , workflowState )
168+ if len (nextActions ) > 0 {
169+ action := PrimaryAction (nextActions )
170+ return PrefixForAction (action ), "?st=unknown"
171+ }
172+ return ":postal_horn:" , "?st=unknown"
173+ }
174+ }
175+
117176// stateParam returns the URL state parameter based on PR analysis.
118177func stateParam (r * turn.CheckResponse ) string {
119178 pr := r .PullRequest
@@ -376,8 +435,8 @@ func PrimaryAction(nextActions map[string]turn.Action) string {
376435
377436// PrefixForAnalysis returns the emoji prefix based on workflow state and next actions.
378437// This is the primary function for determining PR emoji - it handles the logic:
379- // 1. If workflow_state == "newly_published" → ":new:".
380- // 2. Otherwise → emoji based on primary next_action.
438+ // 1. Use WorkflowState as primary signal (most reliable)
439+ // 2. Fall back to NextAction if no WorkflowState
381440func PrefixForAnalysis (workflowState string , nextActions map [string ]turn.Action ) string {
382441 // Log input for debugging emoji selection
383442 actionKinds := make ([]string , 0 , len (nextActions ))
@@ -389,25 +448,27 @@ func PrefixForAnalysis(workflowState string, nextActions map[string]turn.Action)
389448 "next_actions_count" , len (nextActions ),
390449 "next_actions" , actionKinds )
391450
392- // Special case: newly published PRs always show :new:
393- if workflowState == "newly_published" {
394- slog .Debug ("using :new: emoji for newly published PR" )
395- return ":new:"
451+ // Use WorkflowState as primary signal
452+ if workflowState != "" {
453+ emoji , _ := emojiFromWorkflowState (workflowState , nextActions )
454+ slog .Debug ("determined emoji from workflow_state" ,
455+ "workflow_state" , workflowState ,
456+ "emoji" , emoji )
457+ return emoji
396458 }
397459
398- // Determine primary action and return corresponding emoji
460+ // Fallback to NextAction if no WorkflowState
399461 primaryAction := PrimaryAction (nextActions )
400462 if primaryAction != "" {
401463 emoji := PrefixForAction (primaryAction )
402- slog .Debug ("determined emoji from primary action" ,
464+ slog .Debug ("determined emoji from primary action (no workflow_state) " ,
403465 "primary_action" , primaryAction ,
404466 "emoji" , emoji )
405467 return emoji
406468 }
407469
408- // Fallback if no actions
409- slog .Info ("no primary action found - using fallback emoji" ,
410- "workflow_state" , workflowState ,
470+ // Final fallback if no workflow state or actions
471+ slog .Info ("no workflow_state or primary action - using fallback emoji" ,
411472 "next_actions_count" , len (nextActions ),
412473 "fallback_emoji" , ":postal_horn:" )
413474 return ":postal_horn:"
0 commit comments