mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2026-05-04 20:25:07 +00:00
Fully merge statuses (#6119)
This commit is contained in:
@@ -30,6 +30,7 @@ import (
|
||||
"go.woodpecker-ci.org/woodpecker/v3/server/badges"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/server/ccmenu"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/server/model"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/server/pipeline"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/server/store"
|
||||
"go.woodpecker-ci.org/woodpecker/v3/server/store/types"
|
||||
)
|
||||
@@ -96,13 +97,13 @@ func GetBadge(c *gin.Context) {
|
||||
name := "pipeline"
|
||||
var status *model.StatusValue = nil
|
||||
|
||||
pipeline, err := _store.GetPipelineBadge(repo, branch, events)
|
||||
pl, err := _store.GetPipelineBadge(repo, branch, events)
|
||||
if err != nil {
|
||||
if !errors.Is(err, types.RecordNotExist) {
|
||||
log.Warn().Err(err).Msg("could not get last pipeline for badge")
|
||||
}
|
||||
} else {
|
||||
status = &pipeline.Status
|
||||
status = &pl.Status
|
||||
}
|
||||
|
||||
// we serve an SVG, so set content type appropriately.
|
||||
@@ -114,13 +115,16 @@ func GetBadge(c *gin.Context) {
|
||||
name = workflowName
|
||||
status = nil
|
||||
|
||||
workflows, err := _store.WorkflowGetTree(pipeline)
|
||||
workflows, err := _store.WorkflowGetTree(pl)
|
||||
if err == nil {
|
||||
for _, wf := range workflows {
|
||||
if wf.Name == workflowName {
|
||||
stepName := c.Query("step")
|
||||
if len(stepName) == 0 {
|
||||
if status == nil || wf.Failing() {
|
||||
if status != nil {
|
||||
merged := pipeline.MergeStatusValues(*status, wf.State)
|
||||
status = &merged
|
||||
} else {
|
||||
status = &wf.State
|
||||
}
|
||||
continue
|
||||
@@ -129,7 +133,10 @@ func GetBadge(c *gin.Context) {
|
||||
name = workflowName + ": " + stepName
|
||||
for _, s := range wf.Children {
|
||||
if s.Name == stepName {
|
||||
if status == nil || s.Failing() {
|
||||
if status != nil {
|
||||
merged := pipeline.MergeStatusValues(*status, s.State)
|
||||
status = &merged
|
||||
} else {
|
||||
status = &s.State
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,30 +57,3 @@ func IsThereRunningStage(workflows []*Workflow) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// PipelineStatus determine pipeline status based on corresponding workflow list.
|
||||
func PipelineStatus(workflows []*Workflow) StatusValue {
|
||||
status := StatusSuccess
|
||||
|
||||
for _, p := range workflows {
|
||||
if p.Failing() {
|
||||
status = p.State
|
||||
}
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
// WorkflowStatus determine workflow status based on corresponding step list.
|
||||
func WorkflowStatus(steps []*Step) StatusValue {
|
||||
status := StatusSuccess
|
||||
|
||||
for _, p := range steps {
|
||||
if p.Failing() {
|
||||
status = p.State
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
@@ -23,6 +23,17 @@ import (
|
||||
"go.woodpecker-ci.org/woodpecker/v3/server/store"
|
||||
)
|
||||
|
||||
// PipelineStatus determine pipeline status based on corresponding workflow list.
|
||||
func PipelineStatus(workflows []*model.Workflow) model.StatusValue {
|
||||
status := model.StatusSuccess
|
||||
|
||||
for _, p := range workflows {
|
||||
status = MergeStatusValues(status, p.State)
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func UpdateToStatusRunning(store store.Store, pipeline model.Pipeline, started int64) (*model.Pipeline, error) {
|
||||
pipeline.Status = model.StatusRunning
|
||||
pipeline.Started = started
|
||||
|
||||
55
server/pipeline/status.go
Normal file
55
server/pipeline/status.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pipeline
|
||||
|
||||
import "go.woodpecker-ci.org/woodpecker/v3/server/model"
|
||||
|
||||
// list of statuses by their priority. Most important is on top.
|
||||
var statusPriorityOrder = []model.StatusValue{
|
||||
// blocked, declined and created cannot appear in the
|
||||
// same workflow/pipeline at the same time
|
||||
model.StatusDeclined,
|
||||
model.StatusBlocked,
|
||||
model.StatusCreated,
|
||||
|
||||
// errors have highest priority.
|
||||
model.StatusError,
|
||||
|
||||
// skipped and killed cannot appear together with running/pending.
|
||||
model.StatusKilled,
|
||||
model.StatusSkipped,
|
||||
|
||||
// running states
|
||||
model.StatusRunning,
|
||||
model.StatusPending,
|
||||
|
||||
// finished states
|
||||
model.StatusFailure,
|
||||
model.StatusSuccess,
|
||||
}
|
||||
|
||||
var priorityMap map[model.StatusValue]int = buildPriorityMap()
|
||||
|
||||
func buildPriorityMap() map[model.StatusValue]int {
|
||||
m := map[model.StatusValue]int{}
|
||||
for i, s := range statusPriorityOrder {
|
||||
m[s] = i
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func MergeStatusValues(s, t model.StatusValue) model.StatusValue {
|
||||
return statusPriorityOrder[min(priorityMap[s], priorityMap[t])]
|
||||
}
|
||||
76
server/pipeline/status_test.go
Normal file
76
server/pipeline/status_test.go
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2026 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package pipeline
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v3/server/model"
|
||||
)
|
||||
|
||||
func TestStatusValueMerge(t *testing.T) {
|
||||
tests := []struct {
|
||||
s model.StatusValue
|
||||
t model.StatusValue
|
||||
e model.StatusValue
|
||||
}{
|
||||
{
|
||||
s: model.StatusSuccess,
|
||||
t: model.StatusSkipped,
|
||||
e: model.StatusSkipped,
|
||||
},
|
||||
{
|
||||
s: model.StatusSuccess,
|
||||
t: model.StatusSuccess,
|
||||
e: model.StatusSuccess,
|
||||
},
|
||||
{
|
||||
s: model.StatusFailure,
|
||||
t: model.StatusSuccess,
|
||||
e: model.StatusFailure,
|
||||
},
|
||||
{
|
||||
s: model.StatusRunning,
|
||||
t: model.StatusSuccess,
|
||||
e: model.StatusRunning,
|
||||
},
|
||||
{
|
||||
s: model.StatusRunning,
|
||||
t: model.StatusFailure,
|
||||
e: model.StatusRunning,
|
||||
},
|
||||
{
|
||||
s: model.StatusFailure,
|
||||
t: model.StatusKilled,
|
||||
e: model.StatusKilled,
|
||||
},
|
||||
{
|
||||
s: model.StatusSkipped,
|
||||
t: model.StatusKilled,
|
||||
e: model.StatusKilled,
|
||||
},
|
||||
{
|
||||
s: model.StatusSkipped,
|
||||
t: model.StatusSkipped,
|
||||
e: model.StatusSkipped,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
assert.Equal(t, tt.e, MergeStatusValues(tt.s, tt.t))
|
||||
assert.Equal(t, tt.e, MergeStatusValues(tt.t, tt.s))
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,17 @@ import (
|
||||
"go.woodpecker-ci.org/woodpecker/v3/server/store"
|
||||
)
|
||||
|
||||
// WorkflowStatus determine workflow status based on corresponding step list.
|
||||
func WorkflowStatus(steps []*model.Step) model.StatusValue {
|
||||
status := model.StatusSuccess
|
||||
|
||||
for _, p := range steps {
|
||||
status = MergeStatusValues(status, p.State)
|
||||
}
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
func UpdateWorkflowStatusToRunning(store store.Store, workflow model.Workflow, state rpc.WorkflowState) (*model.Workflow, error) {
|
||||
workflow.Started = state.Started
|
||||
workflow.State = model.StatusRunning
|
||||
@@ -37,7 +48,7 @@ func UpdateWorkflowStatusToDone(store store.Store, workflow model.Workflow, stat
|
||||
if state.Started == 0 {
|
||||
workflow.State = model.StatusSkipped
|
||||
} else {
|
||||
workflow.State = model.WorkflowStatus(workflow.Children)
|
||||
workflow.State = WorkflowStatus(workflow.Children)
|
||||
}
|
||||
if workflow.Error != "" {
|
||||
workflow.State = model.StatusFailure
|
||||
|
||||
@@ -369,7 +369,7 @@ func (s *RPC) Done(c context.Context, strWorkflowID string, state rpc.WorkflowSt
|
||||
s.completeChildrenIfParentCompleted(workflow)
|
||||
|
||||
if !model.IsThereRunningStage(currentPipeline.Workflows) {
|
||||
if currentPipeline, err = pipeline.UpdateStatusToDone(s.store, *currentPipeline, model.PipelineStatus(currentPipeline.Workflows), workflow.Finished); err != nil {
|
||||
if currentPipeline, err = pipeline.UpdateStatusToDone(s.store, *currentPipeline, pipeline.PipelineStatus(currentPipeline.Workflows), workflow.Finished); err != nil {
|
||||
logger.Error().Err(err).Msgf("pipeline.UpdateStatusToDone: cannot update workflows final state")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user