Skip to content
4 changes: 4 additions & 0 deletions cmd/server/openapi/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -5529,13 +5529,15 @@ const docTemplate = `{
"success",
"failure",
"killed",
"canceled",
"error",
"blocked",
"declined",
"created"
],
"x-enum-comments": {
"StatusBlocked": "waiting for approval",
"StatusCanceled": "canceled but hasn't been started",
"StatusCreated": "created / internal use only",
"StatusDeclined": "blocked and declined",
"StatusError": "error with the config / while parsing / some other system problem",
Expand All @@ -5553,6 +5555,7 @@ const docTemplate = `{
"successfully finished",
"failed to finish (exit code != 0)",
"killed by user",
"canceled but hasn't been started",
"error with the config / while parsing / some other system problem",
"waiting for approval",
"blocked and declined",
Expand All @@ -5565,6 +5568,7 @@ const docTemplate = `{
"StatusSuccess",
"StatusFailure",
"StatusKilled",
"StatusCanceled",
"StatusError",
"StatusBlocked",
"StatusDeclined",
Expand Down
2 changes: 1 addition & 1 deletion rpc/proto/woodpecker_grpc.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion server/forge/github/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const (
// GitHub commit status.
func convertStatus(status model.StatusValue) string {
switch status {
case model.StatusPending, model.StatusRunning, model.StatusBlocked, model.StatusSkipped:
case model.StatusPending, model.StatusRunning, model.StatusBlocked, model.StatusSkipped, model.StatusCanceled:
return statusPending
case model.StatusFailure, model.StatusDeclined:
return statusFailure
Expand Down
3 changes: 2 additions & 1 deletion server/model/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const (
StatusSuccess StatusValue = "success" // successfully finished
StatusFailure StatusValue = "failure" // failed to finish (exit code != 0)
StatusKilled StatusValue = "killed" // killed by user
StatusCanceled StatusValue = "canceled" // canceled but hasn't been started
StatusError StatusValue = "error" // error with the config / while parsing / some other system problem
StatusBlocked StatusValue = "blocked" // waiting for approval
StatusDeclined StatusValue = "declined" // blocked and declined
Expand All @@ -71,7 +72,7 @@ var ErrInvalidStatusValue = errors.New("invalid status value")

func (s StatusValue) Validate() error {
switch s {
case StatusSkipped, StatusPending, StatusRunning, StatusSuccess, StatusFailure, StatusKilled, StatusError, StatusBlocked, StatusDeclined, StatusCreated:
case StatusSkipped, StatusPending, StatusRunning, StatusSuccess, StatusFailure, StatusKilled, StatusCanceled, StatusError, StatusBlocked, StatusDeclined, StatusCreated:
return nil
default:
return fmt.Errorf("%w: %s", ErrInvalidStatusValue, s)
Expand Down
12 changes: 10 additions & 2 deletions server/pipeline/cancel.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,24 +53,32 @@ func Cancel(ctx context.Context, _forge forge.Forge, store store.Store, repo *mo
}
}

hasPendingOnly := true

// Then update the DB status for pending pipelines
// Running ones will be set when the agents stop on the cancel signal
for _, workflow := range workflows {
if workflow.State == model.StatusPending {
if _, err = UpdateWorkflowToStatusSkipped(store, *workflow); err != nil {
log.Error().Err(err).Msgf("cannot update workflow with id %d state", workflow.ID)
}
} else {
hasPendingOnly = false
}
for _, step := range workflow.Children {
if step.State == model.StatusPending {
if _, err = UpdateStepToStatusSkipped(store, *step, 0); err != nil {
if _, err = UpdateStepToStatusSkipped(store, *step, 0, model.StatusCanceled); err != nil {
log.Error().Err(err).Msgf("cannot update workflow with id %d state", workflow.ID)
}
}
}
}

killedPipeline, err := UpdateToStatusKilled(store, *pipeline, cancelInfo)
plState := model.StatusKilled
if hasPendingOnly {
plState = model.StatusCanceled
}
killedPipeline, err := UpdateToStatusKilled(store, *pipeline, cancelInfo, plState)
if err != nil {
log.Error().Err(err).Msgf("UpdateToStatusKilled: %v", pipeline)
return err
Expand Down
3 changes: 0 additions & 3 deletions server/pipeline/items.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,9 +189,6 @@ func setPipelineStepsOnPipeline(pipeline *model.Pipeline, pipelineItems []*stepb
Failure: step.Failure,
Type: model.StepType(step.Type),
}
if item.Workflow.State == model.StatusSkipped {
step.State = model.StatusSkipped
}
if pipeline.Status == model.StatusBlocked {
step.State = model.StatusBlocked
}
Expand Down
4 changes: 2 additions & 2 deletions server/pipeline/pipeline_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,8 @@ func UpdateToStatusError(store store.Store, pipeline model.Pipeline, err error)
return &pipeline, store.UpdatePipeline(&pipeline)
}

func UpdateToStatusKilled(store store.Store, pipeline model.Pipeline, cancelInfo *model.CancelInfo) (*model.Pipeline, error) {
pipeline.Status = model.StatusKilled
func UpdateToStatusKilled(store store.Store, pipeline model.Pipeline, cancelInfo *model.CancelInfo, state model.StatusValue) (*model.Pipeline, error) {
pipeline.Status = state
pipeline.Finished = time.Now().Unix()
pipeline.CancelInfo = cancelInfo
return &pipeline, store.UpdatePipeline(&pipeline)
Expand Down
2 changes: 1 addition & 1 deletion server/pipeline/pipeline_status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func TestUpdateToStatusKilled(t *testing.T) {
SupersededBy: 2,
}

pipeline, _ := UpdateToStatusKilled(mockStorePipeline(t), model.Pipeline{}, cancelInfo)
pipeline, _ := UpdateToStatusKilled(mockStorePipeline(t), model.Pipeline{}, cancelInfo, model.StatusKilled)

assert.Equal(t, model.StatusKilled, pipeline.Status)
assert.NotNil(t, pipeline.CancelInfo)
Expand Down
16 changes: 15 additions & 1 deletion server/pipeline/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ var statusPriorityOrder = []model.StatusValue{

// skipped and killed cannot appear together with running/pending.
model.StatusKilled,
model.StatusSkipped,
model.StatusCanceled,

// running states
model.StatusRunning,
Expand All @@ -38,6 +38,9 @@ var statusPriorityOrder = []model.StatusValue{
// finished states
model.StatusFailure,
model.StatusSuccess,

// skipped due to status condition
model.StatusSkipped,
}

var priorityMap map[model.StatusValue]int = buildPriorityMap()
Expand All @@ -51,5 +54,16 @@ func buildPriorityMap() map[model.StatusValue]int {
}

func MergeStatusValues(s, t model.StatusValue) model.StatusValue {
// both are skipped due to cancellation -> canceled
if s == model.StatusCanceled && t == model.StatusCanceled {
return model.StatusCanceled
}
// if only one was skipped -> use killed as the workflow/pipeline was running once already
if s == model.StatusCanceled {
s = model.StatusKilled
}
if t == model.StatusCanceled {
t = model.StatusKilled
}
return statusPriorityOrder[min(priorityMap[s], priorityMap[t])]
}
17 changes: 16 additions & 1 deletion server/pipeline/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestStatusValueMerge(t *testing.T) {
{
s: model.StatusSuccess,
t: model.StatusSkipped,
e: model.StatusSkipped,
e: model.StatusSuccess,
},
{
s: model.StatusSuccess,
Expand Down Expand Up @@ -68,6 +68,21 @@ func TestStatusValueMerge(t *testing.T) {
t: model.StatusSkipped,
e: model.StatusSkipped,
},
{
s: model.StatusSkipped,
t: model.StatusCanceled,
e: model.StatusKilled,
},
{
s: model.StatusSuccess,
t: model.StatusCanceled,
e: model.StatusKilled,
},
{
s: model.StatusFailure,
t: model.StatusCanceled,
e: model.StatusKilled,
},
}
for _, tt := range tests {
assert.Equal(t, tt.e, MergeStatusValues(tt.s, tt.t))
Expand Down
4 changes: 2 additions & 2 deletions server/pipeline/step_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ func UpdateStepStatus(store store.Store, step *model.Step, state rpc.StepState)
return store.StepUpdate(step)
}

func UpdateStepToStatusSkipped(store store.Store, step model.Step, finished int64) (*model.Step, error) {
step.State = model.StatusSkipped
func UpdateStepToStatusSkipped(store store.Store, step model.Step, finished int64, status model.StatusValue) (*model.Step, error) {
step.State = status
if step.Started != 0 {
step.State = model.StatusSuccess // for daemons that are killed
step.Finished = finished
Expand Down
4 changes: 2 additions & 2 deletions server/pipeline/step_status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ func TestUpdateStepToStatusSkipped(t *testing.T) {
t.Run("NotStarted", func(t *testing.T) {
t.Parallel()

step, err := UpdateStepToStatusSkipped(mockStoreStep(t), model.Step{}, int64(1))
step, err := UpdateStepToStatusSkipped(mockStoreStep(t), model.Step{}, int64(1), model.StatusSkipped)

assert.NoError(t, err)
assert.Equal(t, model.StatusSkipped, step.State)
Expand All @@ -247,7 +247,7 @@ func TestUpdateStepToStatusSkipped(t *testing.T) {
t.Run("AlreadyStarted", func(t *testing.T) {
t.Parallel()

step, err := UpdateStepToStatusSkipped(mockStoreStep(t), model.Step{Started: 42}, int64(100))
step, err := UpdateStepToStatusSkipped(mockStoreStep(t), model.Step{Started: 42}, int64(100), model.StatusSkipped)

assert.NoError(t, err)
assert.Equal(t, model.StatusSuccess, step.State)
Expand Down
14 changes: 0 additions & 14 deletions server/pipeline/stepbuilder/step_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,6 @@ func (b *StepBuilder) Build() (items []*Item, errorsAndWarnings error) {

items = filterItemsWithMissingDependencies(items)

// check if at least one step can start if slice is not empty
if len(items) > 0 && !workflowListContainsItemsToRun(items) {
return nil, fmt.Errorf("pipeline has no steps to run")
}

return items, errorsAndWarnings
}

Expand Down Expand Up @@ -221,15 +216,6 @@ func (b *StepBuilder) genItemForWorkflow(workflow *model.Workflow, axis matrix.A
return item, errorsAndWarnings
}

func workflowListContainsItemsToRun(items []*Item) bool {
for i := range items {
if items[i].Workflow.State == model.StatusPending {
return true
}
}
return false
}

func filterItemsWithMissingDependencies(items []*Item) []*Item {
itemsToRemove := make([]*Item, 0)

Expand Down
2 changes: 1 addition & 1 deletion server/rpc/rpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ func (s *RPC) checkAgentPermissionByWorkflow(_ context.Context, agent *model.Age
func (s *RPC) completeChildrenIfParentCompleted(completedWorkflow *model.Workflow) {
for _, c := range completedWorkflow.Children {
if c.Running() {
if _, err := pipeline.UpdateStepToStatusSkipped(s.store, *c, completedWorkflow.Finished); err != nil {
if _, err := pipeline.UpdateStepToStatusSkipped(s.store, *c, completedWorkflow.Finished, model.StatusSkipped); err != nil {
log.Error().Err(err).Msgf("done: cannot update step_id %d child state", c.ID)
}
}
Expand Down
8 changes: 7 additions & 1 deletion web/src/components/atomic/Icon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@
:path="mdiRadioboxIndeterminateVariant"
size="1.3rem"
/>
<SvgIcon v-else-if="name === 'status-skipped'" :bg-circle="bgCircle" :path="mdiMinusCircle" size="1.3rem" />
<SvgIcon
v-else-if="name === 'status-skipped' || name === 'status-canceled'"
:bg-circle="bgCircle"
:path="mdiMinusCircle"
size="1.3rem"
/>
<SvgIcon v-else-if="name === 'status-success'" :bg-circle="bgCircle" :path="mdiCheckCircle" size="1.3rem" />
<SvgIcon v-else-if="name === 'alert'" :bg-circle="bgCircle" :path="mdiAlertCircle" size="1.3rem" />
<SvgIcon v-else-if="name === 'question'" :bg-circle="bgCircle" :path="mdiHelpCircle" size="1.3rem" />
Expand Down Expand Up @@ -216,6 +221,7 @@ export type IconNames =
| 'status-skipped'
| 'status-started'
| 'status-success'
| 'status-canceled'
| 'gitea'
| 'gitlab'
| 'bitbucket'
Expand Down
6 changes: 3 additions & 3 deletions web/src/components/repo/pipeline/PipelineLog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
</div>

<div class="text-wp-text-alt-100 m-auto text-xl">
<span v-if="step?.state === 'skipped'">{{ $t('repo.pipeline.actions.canceled') }}</span>
<span v-if="step?.state === 'canceled'">{{ $t('repo.pipeline.actions.canceled') }}</span>
<span v-else-if="!step?.started">{{ $t('repo.pipeline.step_not_started') }}</span>
<div v-else-if="!loadedLogs">{{ $t('repo.pipeline.loading') }}</div>
<div v-else-if="log?.length === 0">{{ $t('repo.pipeline.no_logs') }}</div>
Expand Down Expand Up @@ -174,8 +174,8 @@ const fullscreen = ref(false);
const loadedLogs = computed(() => !!log.value);
const hasLogs = computed(
() =>
// we do not have logs for skipped steps
repo?.value && pipeline.value && step.value && step.value.state !== 'skipped',
// we do not have logs for skipped/canceled steps
repo?.value && pipeline.value && step.value && step.value.state !== 'skipped' && step.value.state !== 'canceled',
);
const autoScroll = useStorage('woodpecker:log-auto-scroll', true);
const showActions = ref(false);
Expand Down
5 changes: 4 additions & 1 deletion web/src/components/repo/pipeline/PipelineStatusIcon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const statusDescriptions = {
pending: t('repo.pipeline.status.pending'),
running: t('repo.pipeline.status.running'),
skipped: t('repo.pipeline.status.skipped'),
canceled: t('repo.pipeline.status.skipped'),
started: t('repo.pipeline.status.started'),
success: t('repo.pipeline.status.success'),
} satisfies {
Expand All @@ -52,6 +53,8 @@ const statusDescriptions = {
};

const shouldShowBgCircle = computed(() => {
return service ? false : ['blocked', 'declined', 'error', 'failure', 'killed', 'skipped', 'success'].includes(status);
return service
? false
: ['blocked', 'declined', 'error', 'failure', 'killed', 'skipped', 'canceled', 'success'].includes(status);
});
</script>
1 change: 1 addition & 0 deletions web/src/components/repo/pipeline/pipeline-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const pipelineStatusColors: Record<PipelineStatus, 'green' | 'gray' | 're
killed: 'gray',
pending: 'orange',
skipped: 'gray',
canceled: 'gray',
running: 'blue',
started: 'blue',
success: 'green',
Expand Down
3 changes: 2 additions & 1 deletion web/src/lib/api/types/pipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ export type PipelineStatus =
| 'running'
| 'skipped'
| 'started'
| 'success';
| 'success'
| 'canceled';

export interface PipelineWorkflow {
id: number;
Expand Down