feat(webhook): add workflow_step webhook events for step-level CI notifications

Adds HookEventWorkflowStep event type that fires on every step state
transition (queued -> in_progress -> completed). Follows the same
pattern as the existing workflow_job events.

- New WorkflowStepPayload struct with run/job/step context
- WorkflowStepStatusUpdate notifier interface + dispatch
- Step state change detection in UpdateTask runner endpoint
- Fix: register workflow_step in updateHookEvents API mapping
- Full test coverage mirroring workflow_job tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 17:47:14 -05:00
parent a39af1a829
commit bb6faa4c5d
30 changed files with 504 additions and 3 deletions
+119
View File
@@ -610,6 +610,125 @@ func TestGetReleasePayloadInfo(t *testing.T) {
}
}
func workflowJobTestPayload() *api.WorkflowJobPayload {
return &api.WorkflowJobPayload{
Action: "completed",
WorkflowJob: &api.ActionWorkflowJob{
ID: 1,
Name: "build",
Status: "completed",
RunID: 42,
HeadSha: "abc123",
HTMLURL: "http://localhost:3000/test/repo/actions/runs/42/jobs/1",
},
Repo: &api.Repository{
HTMLURL: "http://localhost:3000/test/repo",
Name: "repo",
FullName: "test/repo",
},
Sender: &api.User{
UserName: "user1",
AvatarURL: "http://localhost:3000/user1/avatar",
},
}
}
func workflowStepTestPayload() *api.WorkflowStepPayload {
return &api.WorkflowStepPayload{
Action: "completed",
WorkflowRun: &api.ActionWorkflowRun{
ID: 42,
DisplayTitle: "Push: main",
},
WorkflowJob: &api.ActionWorkflowJob{
ID: 1,
Name: "build",
Status: "completed",
RunID: 42,
HeadSha: "abc123",
HTMLURL: "http://localhost:3000/test/repo/actions/runs/42/jobs/1",
},
Step: &api.ActionWorkflowStep{
Name: "Run tests",
Number: 0,
Status: "completed",
Conclusion: "success",
},
Repo: &api.Repository{
HTMLURL: "http://localhost:3000/test/repo",
Name: "repo",
FullName: "test/repo",
},
Sender: &api.User{
UserName: "user1",
AvatarURL: "http://localhost:3000/user1/avatar",
},
}
}
func TestGetWorkflowStepPayloadInfo(t *testing.T) {
cases := []struct {
action string
status string
conclusion string
expectText string
expectColor int
}{
{
action: "queued",
status: "queued",
conclusion: "",
expectText: "Workflow Step queued: build / Run tests:queued",
expectColor: orangeColorLight,
},
{
action: "in_progress",
status: "in_progress",
conclusion: "",
expectText: "Workflow Step in_progress: build / Run tests:in_progress",
expectColor: greyColor,
},
{
action: "completed",
status: "completed",
conclusion: "success",
expectText: "Workflow Step completed: build / Run tests:success",
expectColor: greenColor,
},
{
action: "completed",
status: "completed",
conclusion: "failure",
expectText: "Workflow Step completed: build / Run tests:failure",
expectColor: redColor,
},
{
action: "completed",
status: "completed",
conclusion: "cancelled",
expectText: "Workflow Step completed: build / Run tests:cancelled",
expectColor: yellowColor,
},
{
action: "completed",
status: "completed",
conclusion: "skipped",
expectText: "Workflow Step completed: build / Run tests:skipped",
expectColor: purpleColor,
},
}
for i, c := range cases {
p := workflowStepTestPayload()
p.Action = c.action
p.Step.Status = c.status
p.Step.Conclusion = c.conclusion
text, color := getWorkflowStepPayloadInfo(p, noneLinkFormatter, false)
assert.Equal(t, c.expectText, text, "case %d", i)
assert.Equal(t, c.expectColor, color, "case %d", i)
}
}
func TestGetIssueCommentPayloadInfo(t *testing.T) {
p := pullRequestCommentTestPayload()