Fully merge statuses (#6119)

This commit is contained in:
qwerty287
2026-02-22 12:17:41 +01:00
committed by GitHub
parent 95048409f2
commit e2d6c712ba
7 changed files with 167 additions and 34 deletions

View File

@@ -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
}
}

View File

@@ -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
}

View File

@@ -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
View 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])]
}

View 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))
}
}

View File

@@ -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

View File

@@ -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")
}
}