Skip to content

Commit

Permalink
[BIVS-2846] Accept build trigger responses with multiple results (#207)
Browse files Browse the repository at this point in the history
* BIVS-2846 accept build trigger responses with multiple results

* lint
  • Loading branch information
molnarm authored Oct 29, 2024
1 parent 51b044e commit 21e923d
Show file tree
Hide file tree
Showing 3 changed files with 306 additions and 18 deletions.
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

0 comments on commit 21e923d

Please sign in to comment.