Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BIVS-2846] Accept build trigger responses with multiple results #207

Merged
merged 2 commits into from
Oct 29, 2024
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
20 changes: 18 additions & 2 deletions bitriseapi/bitriseapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,14 +105,30 @@ type TriggerAPIParamsModel struct {

// TriggerAPIResponseModel ...
type TriggerAPIResponseModel struct {
Status string `json:"status"`
Message string `json:"message"`
Service string `json:"service"`
AppSlug string `json:"slug"`
// Deprecated
BuildSlug string `json:"build_slug"`
// Deprecated
BuildNumber int `json:"build_number"`
// Deprecated
BuildURL string `json:"build_url"`
// Deprecated
TriggeredWorkflow string `json:"triggered_workflow"`
Results []BuildTriggerRespItemModel `json:"results"`
}

// BuildTriggerRespItemModel ...
type BuildTriggerRespItemModel struct {
Status string `json:"status"`
Message string `json:"message"`
Service string `json:"service"`
AppSlug string `json:"slug"`
BuildSlug string `json:"build_slug"`
BuildNumber int `json:"build_number"`
BuildURL string `json:"build_url"`
TriggeredWorkflow string `json:"triggered_workflow"`
TriggeredPipeline string `json:"triggered_pipeline"`
}

// Validate ...
Expand Down
59 changes: 48 additions & 11 deletions service/hook/slack/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,43 @@ type RespModel struct {
Attachments []AttachmentItemModel `json:"attachments,omitempty"`
}

func messageForSuccessfulBuildTrigger(apiResponse bitriseapi.TriggerAPIResponseModel) string {
return fmt.Sprintf("Triggered build #%d (%s), with workflow: %s - url: %s",
apiResponse.BuildNumber,
apiResponse.BuildSlug,
apiResponse.TriggeredWorkflow,
apiResponse.BuildURL)
func messageForBuildTrigger(apiResponse bitriseapi.TriggerAPIResponseModel) string {
if len(apiResponse.Results) < 2 {
// Single successful build
return fmt.Sprintf("Triggered build #%d (%s), with workflow: %s - url: %s",
apiResponse.BuildNumber,
apiResponse.BuildSlug,
apiResponse.TriggeredWorkflow,
apiResponse.BuildURL)
}

// Multiple builds, maybe failing
msg := fmt.Sprintf("Triggered %d builds:", len(apiResponse.Results))
for _, result := range apiResponse.Results {
var targetType, targetName string
if result.TriggeredPipeline == "" {
targetType = "workflow"
targetName = result.TriggeredWorkflow
} else {
targetType = "pipeline"
targetName = result.TriggeredPipeline
}

if result.Status == "ok" {
msg += fmt.Sprintf("\nbuild #%d (%s), with %s: %s - url: %s",
result.BuildNumber,
result.BuildSlug,
targetType,
targetName,
result.BuildURL)
} else {
msg += fmt.Sprintf("\nbuild with %s: %s - failed: %s",
targetType,
targetName,
result.Message)
}
}
return msg
}

// TransformResponse ...
Expand All @@ -212,11 +243,17 @@ func (hp HookProvider) TransformResponse(input hookCommon.TransformResponseInput
}
if len(input.FailedTriggerResponses) > 0 {
for _, aFailedTrigResp := range input.FailedTriggerResponses {
errMsg := aFailedTrigResp.Message
if errMsg == "" {
errMsg = fmt.Sprintf("%+v", aFailedTrigResp)
if len(aFailedTrigResp.Results) > 1 {
// New behaviour: multiple builds, some have failed
slackAttachments = append(slackAttachments, createAttachmentItemModel(messageForBuildTrigger(aFailedTrigResp), slackColorDanger))
} else {
// Compatibility behaviour: for a project-level error or a single build, report errors as before
errMsg := aFailedTrigResp.Message
if errMsg == "" {
errMsg = fmt.Sprintf("%+v", aFailedTrigResp)
}
slackAttachments = append(slackAttachments, createAttachmentItemModel(errMsg, slackColorDanger))
}
slackAttachments = append(slackAttachments, createAttachmentItemModel(errMsg, slackColorDanger))
}
}
if len(input.SkippedTriggerResponses) > 0 {
Expand All @@ -230,7 +267,7 @@ func (hp HookProvider) TransformResponse(input hookCommon.TransformResponseInput
}
if len(input.SuccessTriggerResponses) > 0 {
for _, aSuccessTrigResp := range input.SuccessTriggerResponses {
slackAttachments = append(slackAttachments, createAttachmentItemModel(messageForSuccessfulBuildTrigger(aSuccessTrigResp), slackColorGood))
slackAttachments = append(slackAttachments, createAttachmentItemModel(messageForBuildTrigger(aSuccessTrigResp), slackColorGood))
}
}

Expand Down
245 changes: 240 additions & 5 deletions service/hook/slack/slack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ func Test_HookProvider_TransformRequest(t *testing.T) {

func Test_messageForSuccessfulBuildTrigger(t *testing.T) {
require.Equal(t, "Triggered build #23 (build-slug), with workflow: test-wf - url: bitrise.io/...",
messageForSuccessfulBuildTrigger(bitriseapi.TriggerAPIResponseModel{
messageForBuildTrigger(bitriseapi.TriggerAPIResponseModel{
Status: "ok",
Message: "some msg from the server",
Service: "bitrise",
Expand All @@ -492,7 +492,7 @@ func Test_messageForSuccessfulBuildTrigger(t *testing.T) {
func Test_HookProvider_TransformResponse(t *testing.T) {
provider := HookProvider{}

t.Log("Single success")
t.Log("Single response: legacy single success")
{
baseRespModel := hookCommon.TransformResponseInputModel{
SuccessTriggerResponses: []bitriseapi.TriggerAPIResponseModel{
Expand Down Expand Up @@ -527,7 +527,7 @@ func Test_HookProvider_TransformResponse(t *testing.T) {
}, resp)
}

t.Log("Single failed trigger - with defined 'message'")
t.Log("Single response: legacy single failed trigger - with defined 'message'")
{
baseRespModel := hookCommon.TransformResponseInputModel{
FailedTriggerResponses: []bitriseapi.TriggerAPIResponseModel{
Expand Down Expand Up @@ -560,7 +560,7 @@ func Test_HookProvider_TransformResponse(t *testing.T) {
}, resp)
}

t.Log("Single failed trigger - empty 'message'")
t.Log("Single response: legacy single failed trigger - empty 'message'")
{
baseRespModel := hookCommon.TransformResponseInputModel{
FailedTriggerResponses: []bitriseapi.TriggerAPIResponseModel{
Expand All @@ -576,7 +576,242 @@ func Test_HookProvider_TransformResponse(t *testing.T) {
}

resp := provider.TransformResponse(baseRespModel)
expectedText := `{Status:error Message: Service:bitrise AppSlug:app-slug BuildSlug:build-slug BuildNumber:23 BuildURL: TriggeredWorkflow:}`
expectedText := `{Status:error Message: Service:bitrise AppSlug:app-slug BuildSlug:build-slug BuildNumber:23 BuildURL: TriggeredWorkflow: Results:[]}`
require.Equal(t, hookCommon.TransformResponseModel{
Data: RespModel{
ResponseType: "in_channel",
Text: "",
Attachments: []AttachmentItemModel{
{
Text: expectedText,
Fallback: expectedText,
Color: slackColorDanger,
},
},
},
HTTPStatusCode: 200,
}, resp)
}

t.Log("Single response: single success")
{
baseRespModel := hookCommon.TransformResponseInputModel{
SuccessTriggerResponses: []bitriseapi.TriggerAPIResponseModel{
{
Status: "ok",
Message: "triggered build",
Service: "bitrise",
AppSlug: "app-slug",
BuildSlug: "build-slug",
BuildNumber: 23,
BuildURL: "bitrise.io/...",
TriggeredWorkflow: "wf-one",
Results: []bitriseapi.BuildTriggerRespItemModel{
{
Status: "ok",
BuildSlug: "build-slug",
BuildNumber: 23,
BuildURL: "bitrise.io/...",
TriggeredWorkflow: "wf-one",
TriggeredPipeline: "",
},
},
},
},
}

resp := provider.TransformResponse(baseRespModel)
expectedText := `Triggered build #23 (build-slug), with workflow: wf-one - url: bitrise.io/...`
require.Equal(t, hookCommon.TransformResponseModel{
Data: RespModel{
ResponseType: "in_channel",
Text: "",
Attachments: []AttachmentItemModel{
{
Text: expectedText,
Fallback: expectedText,
Color: slackColorGood,
},
},
},
HTTPStatusCode: 200,
}, resp)
}

t.Log("Single response: single failed build")
{
baseRespModel := hookCommon.TransformResponseInputModel{
FailedTriggerResponses: []bitriseapi.TriggerAPIResponseModel{
{
Status: "error",
Message: "failed build",
Service: "bitrise",
AppSlug: "app-slug",
Results: []bitriseapi.BuildTriggerRespItemModel{
{
Status: "error",
Message: "failed build",
TriggeredWorkflow: "wf-one",
TriggeredPipeline: "",
},
},
},
},
}

resp := provider.TransformResponse(baseRespModel)
expectedText := "failed build"
require.Equal(t, hookCommon.TransformResponseModel{
Data: RespModel{
ResponseType: "in_channel",
Text: "",
Attachments: []AttachmentItemModel{
{
Text: expectedText,
Fallback: expectedText,
Color: slackColorDanger,
},
},
},
HTTPStatusCode: 200,
}, resp)
}

t.Log("Single response: multiple successful builds")
{
baseRespModel := hookCommon.TransformResponseInputModel{
SuccessTriggerResponses: []bitriseapi.TriggerAPIResponseModel{
{
Status: "ok",
Message: "triggered build",
Service: "bitrise",
AppSlug: "app-slug",
BuildSlug: "build-slug",
BuildNumber: 23,
BuildURL: "bitrise.io/...",
TriggeredWorkflow: "wf-one",
Results: []bitriseapi.BuildTriggerRespItemModel{
{
Status: "ok",
BuildSlug: "build-slug",
BuildNumber: 23,
BuildURL: "bitrise.io/...",
TriggeredWorkflow: "wf-one",
TriggeredPipeline: "",
},
{
Status: "ok",
BuildSlug: "second-build",
BuildNumber: 46,
BuildURL: "bitrise.io/....",
TriggeredWorkflow: "",
TriggeredPipeline: "pipeline-one",
},
},
},
},
}

resp := provider.TransformResponse(baseRespModel)
expectedText := "Triggered 2 builds:\nbuild #23 (build-slug), with workflow: wf-one - url: bitrise.io/...\nbuild #46 (second-build), with pipeline: pipeline-one - url: bitrise.io/...."
require.Equal(t, hookCommon.TransformResponseModel{
Data: RespModel{
ResponseType: "in_channel",
Text: "",
Attachments: []AttachmentItemModel{
{
Text: expectedText,
Fallback: expectedText,
Color: slackColorGood,
},
},
},
HTTPStatusCode: 200,
}, resp)
}

t.Log("Single response: multiple failed builds")
{
baseRespModel := hookCommon.TransformResponseInputModel{
FailedTriggerResponses: []bitriseapi.TriggerAPIResponseModel{
{
Status: "error",
Message: "failed build",
Service: "bitrise",
AppSlug: "app-slug",
Results: []bitriseapi.BuildTriggerRespItemModel{
{
Status: "error",
Message: "failed build",
TriggeredWorkflow: "wf-one",
TriggeredPipeline: "",
},
{
Status: "error",
Message: "this failed too",
TriggeredWorkflow: "",
TriggeredPipeline: "pipeline-one",
},
},
},
},
}

resp := provider.TransformResponse(baseRespModel)
expectedText := "Triggered 2 builds:\nbuild with workflow: wf-one - failed: failed build\nbuild with pipeline: pipeline-one - failed: this failed too"
require.Equal(t, hookCommon.TransformResponseModel{
Data: RespModel{
ResponseType: "in_channel",
Text: "",
Attachments: []AttachmentItemModel{
{
Text: expectedText,
Fallback: expectedText,
Color: slackColorDanger,
},
},
},
HTTPStatusCode: 200,
}, resp)
}

t.Log("Single response: multiple builds with mixed status")
{
baseRespModel := hookCommon.TransformResponseInputModel{
FailedTriggerResponses: []bitriseapi.TriggerAPIResponseModel{
{
Status: "error",
Message: "failed build",
Service: "bitrise",
AppSlug: "app-slug",
Results: []bitriseapi.BuildTriggerRespItemModel{
{
Status: "error",
Message: "failed build",
TriggeredWorkflow: "wf-one",
TriggeredPipeline: "",
},
{
Status: "ok",
BuildSlug: "build-slug",
BuildNumber: 23,
BuildURL: "bitrise.io/...",
TriggeredWorkflow: "wf-one",
TriggeredPipeline: "",
},
{
Status: "error",
Message: "this failed too",
TriggeredWorkflow: "",
TriggeredPipeline: "pipeline-one",
},
},
},
},
}

resp := provider.TransformResponse(baseRespModel)
expectedText := "Triggered 3 builds:\nbuild with workflow: wf-one - failed: failed build\nbuild #23 (build-slug), with workflow: wf-one - url: bitrise.io/...\nbuild with pipeline: pipeline-one - failed: this failed too"
require.Equal(t, hookCommon.TransformResponseModel{
Data: RespModel{
ResponseType: "in_channel",
Expand Down