From 4d193016100e5d7af6883a7674c7f78587f7a668 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Sun, 1 Mar 2026 09:24:23 +0100 Subject: [PATCH 01/11] first parts --- server/forge/github/convert.go | 2 +- server/model/const.go | 3 ++- server/pipeline/cancel.go | 2 +- server/pipeline/status.go | 5 ++++- server/pipeline/step_status.go | 4 ++-- server/pipeline/step_status_test.go | 4 ++-- server/rpc/rpc.go | 3 ++- 7 files changed, 14 insertions(+), 9 deletions(-) diff --git a/server/forge/github/convert.go b/server/forge/github/convert.go index 7bec159eba1..c4eaffb47e6 100644 --- a/server/forge/github/convert.go +++ b/server/forge/github/convert.go @@ -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.StatusCancelled: return statusPending case model.StatusFailure, model.StatusDeclined: return statusFailure diff --git a/server/model/const.go b/server/model/const.go index 6b35782c042..bbf8eb551f4 100644 --- a/server/model/const.go +++ b/server/model/const.go @@ -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 + StatusCancelled StatusValue = "cancelled" // cancelled 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 @@ -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, StatusCancelled, StatusError, StatusBlocked, StatusDeclined, StatusCreated: return nil default: return fmt.Errorf("%w: %s", ErrInvalidStatusValue, s) diff --git a/server/pipeline/cancel.go b/server/pipeline/cancel.go index a55031f8e78..9d9f29f179c 100644 --- a/server/pipeline/cancel.go +++ b/server/pipeline/cancel.go @@ -63,7 +63,7 @@ func Cancel(ctx context.Context, _forge forge.Forge, store store.Store, repo *mo } 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.StatusCancelled); err != nil { log.Error().Err(err).Msgf("cannot update workflow with id %d state", workflow.ID) } } diff --git a/server/pipeline/status.go b/server/pipeline/status.go index 4efdf1c1359..61f1c2659d3 100644 --- a/server/pipeline/status.go +++ b/server/pipeline/status.go @@ -29,7 +29,7 @@ var statusPriorityOrder = []model.StatusValue{ // skipped and killed cannot appear together with running/pending. model.StatusKilled, - model.StatusSkipped, + model.StatusCancelled, // TODO if has both cancelled: final status should be killed (if not both are cancelled) // running states model.StatusRunning, @@ -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() diff --git a/server/pipeline/step_status.go b/server/pipeline/step_status.go index a45c1361601..5d2b5ddf944 100644 --- a/server/pipeline/step_status.go +++ b/server/pipeline/step_status.go @@ -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 diff --git a/server/pipeline/step_status_test.go b/server/pipeline/step_status_test.go index 2a00aef8797..52db2eb8f56 100644 --- a/server/pipeline/step_status_test.go +++ b/server/pipeline/step_status_test.go @@ -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) @@ -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) diff --git a/server/rpc/rpc.go b/server/rpc/rpc.go index 6fa97735d73..2f479554f68 100644 --- a/server/rpc/rpc.go +++ b/server/rpc/rpc.go @@ -559,7 +559,8 @@ 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 { + // TODO should this be skipped or cancelled? (i guess skipped) + 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) } } From 1711ea3420644b3bf7c23bc5853d8c492d55c6dd Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Sun, 1 Mar 2026 09:26:43 +0100 Subject: [PATCH 02/11] unnecessary code --- server/pipeline/items.go | 3 --- server/pipeline/stepbuilder/step_builder.go | 11 +---------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/server/pipeline/items.go b/server/pipeline/items.go index 4cf3c2dd044..d0bf26b3b7d 100644 --- a/server/pipeline/items.go +++ b/server/pipeline/items.go @@ -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 } diff --git a/server/pipeline/stepbuilder/step_builder.go b/server/pipeline/stepbuilder/step_builder.go index f98ca4f7491..9d45cde0f27 100644 --- a/server/pipeline/stepbuilder/step_builder.go +++ b/server/pipeline/stepbuilder/step_builder.go @@ -109,7 +109,7 @@ 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) { + if len(items) > 0 { return nil, fmt.Errorf("pipeline has no steps to run") } @@ -221,15 +221,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) From 61fa4b660887834cbfc2c3c6f48e96419b0f2d90 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Sun, 1 Mar 2026 09:33:40 +0100 Subject: [PATCH 03/11] correctly merge statuses --- server/pipeline/status.go | 13 ++++++++++++- server/pipeline/status_test.go | 17 ++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/server/pipeline/status.go b/server/pipeline/status.go index 61f1c2659d3..d7e7abb6be3 100644 --- a/server/pipeline/status.go +++ b/server/pipeline/status.go @@ -29,7 +29,7 @@ var statusPriorityOrder = []model.StatusValue{ // skipped and killed cannot appear together with running/pending. model.StatusKilled, - model.StatusCancelled, // TODO if has both cancelled: final status should be killed (if not both are cancelled) + model.StatusCancelled, // running states model.StatusRunning, @@ -54,5 +54,16 @@ func buildPriorityMap() map[model.StatusValue]int { } func MergeStatusValues(s, t model.StatusValue) model.StatusValue { + // both are skipped due to cancellation -> cancelled + if s == model.StatusCancelled && t == model.StatusCancelled { + return model.StatusCancelled + } + // if only one was skipped -> use killed as the workflow/pipeline was running once already + if s == model.StatusCancelled { + s = model.StatusKilled + } + if t == model.StatusCancelled { + t = model.StatusKilled + } return statusPriorityOrder[min(priorityMap[s], priorityMap[t])] } diff --git a/server/pipeline/status_test.go b/server/pipeline/status_test.go index 18171d281cc..18af274b021 100644 --- a/server/pipeline/status_test.go +++ b/server/pipeline/status_test.go @@ -31,7 +31,7 @@ func TestStatusValueMerge(t *testing.T) { { s: model.StatusSuccess, t: model.StatusSkipped, - e: model.StatusSkipped, + e: model.StatusSuccess, }, { s: model.StatusSuccess, @@ -68,6 +68,21 @@ func TestStatusValueMerge(t *testing.T) { t: model.StatusSkipped, e: model.StatusSkipped, }, + { + s: model.StatusSkipped, + t: model.StatusCancelled, + e: model.StatusCancelled, + }, + { + s: model.StatusSuccess, + t: model.StatusCancelled, + e: model.StatusKilled, + }, + { + s: model.StatusFailure, + t: model.StatusCancelled, + e: model.StatusKilled, + }, } for _, tt := range tests { assert.Equal(t, tt.e, MergeStatusValues(tt.s, tt.t)) From 51cd740324b83af468a0f56b8d42d91bb4f667d8 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Sun, 1 Mar 2026 11:11:36 +0100 Subject: [PATCH 04/11] fix tests --- server/pipeline/status_test.go | 2 +- server/pipeline/stepbuilder/step_builder.go | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/server/pipeline/status_test.go b/server/pipeline/status_test.go index 18af274b021..cd5fece4d1d 100644 --- a/server/pipeline/status_test.go +++ b/server/pipeline/status_test.go @@ -71,7 +71,7 @@ func TestStatusValueMerge(t *testing.T) { { s: model.StatusSkipped, t: model.StatusCancelled, - e: model.StatusCancelled, + e: model.StatusKilled, }, { s: model.StatusSuccess, diff --git a/server/pipeline/stepbuilder/step_builder.go b/server/pipeline/stepbuilder/step_builder.go index 9d45cde0f27..42b0b3bc7dc 100644 --- a/server/pipeline/stepbuilder/step_builder.go +++ b/server/pipeline/stepbuilder/step_builder.go @@ -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 { - return nil, fmt.Errorf("pipeline has no steps to run") - } - return items, errorsAndWarnings } From 891622badd9bd0346950c651849dd09ad865c3fd Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Sun, 1 Mar 2026 11:13:03 +0100 Subject: [PATCH 05/11] fix lint --- server/forge/github/convert.go | 2 +- server/model/const.go | 4 ++-- server/pipeline/cancel.go | 2 +- server/pipeline/status.go | 12 ++++++------ server/pipeline/status_test.go | 6 +++--- server/rpc/rpc.go | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/server/forge/github/convert.go b/server/forge/github/convert.go index c4eaffb47e6..ad85889b23e 100644 --- a/server/forge/github/convert.go +++ b/server/forge/github/convert.go @@ -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, model.StatusCancelled: + case model.StatusPending, model.StatusRunning, model.StatusBlocked, model.StatusSkipped, model.StatusCanceled: return statusPending case model.StatusFailure, model.StatusDeclined: return statusFailure diff --git a/server/model/const.go b/server/model/const.go index bbf8eb551f4..124f07d7f95 100644 --- a/server/model/const.go +++ b/server/model/const.go @@ -61,7 +61,7 @@ const ( StatusSuccess StatusValue = "success" // successfully finished StatusFailure StatusValue = "failure" // failed to finish (exit code != 0) StatusKilled StatusValue = "killed" // killed by user - StatusCancelled StatusValue = "cancelled" // cancelled but hasn't been started + 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 @@ -72,7 +72,7 @@ var ErrInvalidStatusValue = errors.New("invalid status value") func (s StatusValue) Validate() error { switch s { - case StatusSkipped, StatusPending, StatusRunning, StatusSuccess, StatusFailure, StatusKilled, StatusCancelled, 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) diff --git a/server/pipeline/cancel.go b/server/pipeline/cancel.go index 9d9f29f179c..02f2d5cf521 100644 --- a/server/pipeline/cancel.go +++ b/server/pipeline/cancel.go @@ -63,7 +63,7 @@ func Cancel(ctx context.Context, _forge forge.Forge, store store.Store, repo *mo } for _, step := range workflow.Children { if step.State == model.StatusPending { - if _, err = UpdateStepToStatusSkipped(store, *step, 0, model.StatusCancelled); 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) } } diff --git a/server/pipeline/status.go b/server/pipeline/status.go index d7e7abb6be3..8b06f9bd38e 100644 --- a/server/pipeline/status.go +++ b/server/pipeline/status.go @@ -29,7 +29,7 @@ var statusPriorityOrder = []model.StatusValue{ // skipped and killed cannot appear together with running/pending. model.StatusKilled, - model.StatusCancelled, + model.StatusCanceled, // running states model.StatusRunning, @@ -54,15 +54,15 @@ func buildPriorityMap() map[model.StatusValue]int { } func MergeStatusValues(s, t model.StatusValue) model.StatusValue { - // both are skipped due to cancellation -> cancelled - if s == model.StatusCancelled && t == model.StatusCancelled { - return model.StatusCancelled + // 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.StatusCancelled { + if s == model.StatusCanceled { s = model.StatusKilled } - if t == model.StatusCancelled { + if t == model.StatusCanceled { t = model.StatusKilled } return statusPriorityOrder[min(priorityMap[s], priorityMap[t])] diff --git a/server/pipeline/status_test.go b/server/pipeline/status_test.go index cd5fece4d1d..1268da7c257 100644 --- a/server/pipeline/status_test.go +++ b/server/pipeline/status_test.go @@ -70,17 +70,17 @@ func TestStatusValueMerge(t *testing.T) { }, { s: model.StatusSkipped, - t: model.StatusCancelled, + t: model.StatusCanceled, e: model.StatusKilled, }, { s: model.StatusSuccess, - t: model.StatusCancelled, + t: model.StatusCanceled, e: model.StatusKilled, }, { s: model.StatusFailure, - t: model.StatusCancelled, + t: model.StatusCanceled, e: model.StatusKilled, }, } diff --git a/server/rpc/rpc.go b/server/rpc/rpc.go index 2f479554f68..eae0fdb0e27 100644 --- a/server/rpc/rpc.go +++ b/server/rpc/rpc.go @@ -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() { - // TODO should this be skipped or cancelled? (i guess skipped) + // TODO should this be skipped or canceled? 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) } From c09cab3d132096e6ada30d546cfce3731dbe0ed0 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Sun, 1 Mar 2026 11:22:16 +0100 Subject: [PATCH 06/11] update web --- web/src/components/atomic/Icon.vue | 8 +++++++- web/src/components/repo/pipeline/PipelineStatusIcon.vue | 5 ++++- web/src/components/repo/pipeline/pipeline-status.ts | 1 + web/src/lib/api/types/pipeline.ts | 3 ++- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/web/src/components/atomic/Icon.vue b/web/src/components/atomic/Icon.vue index d04e1a51baa..9c3d68cd869 100644 --- a/web/src/components/atomic/Icon.vue +++ b/web/src/components/atomic/Icon.vue @@ -48,7 +48,12 @@ :path="mdiRadioboxIndeterminateVariant" size="1.3rem" /> - + @@ -216,6 +221,7 @@ export type IconNames = | 'status-skipped' | 'status-started' | 'status-success' + | 'status-canceled' | 'gitea' | 'gitlab' | 'bitbucket' diff --git a/web/src/components/repo/pipeline/PipelineStatusIcon.vue b/web/src/components/repo/pipeline/PipelineStatusIcon.vue index e32ac798d19..106a6b27d85 100644 --- a/web/src/components/repo/pipeline/PipelineStatusIcon.vue +++ b/web/src/components/repo/pipeline/PipelineStatusIcon.vue @@ -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 { @@ -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); }); diff --git a/web/src/components/repo/pipeline/pipeline-status.ts b/web/src/components/repo/pipeline/pipeline-status.ts index b69c0463448..aa4be57f29a 100644 --- a/web/src/components/repo/pipeline/pipeline-status.ts +++ b/web/src/components/repo/pipeline/pipeline-status.ts @@ -8,6 +8,7 @@ export const pipelineStatusColors: Record Date: Mon, 2 Mar 2026 11:28:33 +0100 Subject: [PATCH 07/11] openapi --- cmd/server/openapi/docs.go | 4 ++++ rpc/proto/woodpecker_grpc.pb.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/server/openapi/docs.go b/cmd/server/openapi/docs.go index c5c78746bfd..68ce140b637 100644 --- a/cmd/server/openapi/docs.go +++ b/cmd/server/openapi/docs.go @@ -5529,6 +5529,7 @@ const docTemplate = `{ "success", "failure", "killed", + "canceled", "error", "blocked", "declined", @@ -5536,6 +5537,7 @@ const docTemplate = `{ ], "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", @@ -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", @@ -5565,6 +5568,7 @@ const docTemplate = `{ "StatusSuccess", "StatusFailure", "StatusKilled", + "StatusCanceled", "StatusError", "StatusBlocked", "StatusDeclined", diff --git a/rpc/proto/woodpecker_grpc.pb.go b/rpc/proto/woodpecker_grpc.pb.go index 88ca6e7a965..4dfd78a0efb 100644 --- a/rpc/proto/woodpecker_grpc.pb.go +++ b/rpc/proto/woodpecker_grpc.pb.go @@ -15,7 +15,7 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.6.0 +// - protoc-gen-go-grpc v1.6.1 // - protoc v6.33.1 // source: woodpecker.proto From bc561d54ea4ec39ced76cf9ac8d349871600ed61 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Mon, 2 Mar 2026 11:43:36 +0100 Subject: [PATCH 08/11] fix web --- web/src/components/repo/pipeline/PipelineLog.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/components/repo/pipeline/PipelineLog.vue b/web/src/components/repo/pipeline/PipelineLog.vue index 7769509316c..d25d4771b91 100644 --- a/web/src/components/repo/pipeline/PipelineLog.vue +++ b/web/src/components/repo/pipeline/PipelineLog.vue @@ -101,7 +101,7 @@
- {{ $t('repo.pipeline.actions.canceled') }} + {{ $t('repo.pipeline.actions.canceled') }} {{ $t('repo.pipeline.step_not_started') }}
{{ $t('repo.pipeline.loading') }}
{{ $t('repo.pipeline.no_logs') }}
@@ -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); From 1454c2f7a40fbe0f1d15d4b3ff935bc0c13a6740 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Mon, 2 Mar 2026 11:53:29 +0100 Subject: [PATCH 09/11] fix status if canceled pipeline hasn't started --- server/pipeline/cancel.go | 10 +++++++++- server/pipeline/pipeline_status.go | 4 ++-- server/pipeline/pipeline_status_test.go | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/server/pipeline/cancel.go b/server/pipeline/cancel.go index 02f2d5cf521..e82c2a25848 100644 --- a/server/pipeline/cancel.go +++ b/server/pipeline/cancel.go @@ -53,6 +53,8 @@ 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 { @@ -60,6 +62,8 @@ func Cancel(ctx context.Context, _forge forge.Forge, store store.Store, repo *mo 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 { @@ -70,7 +74,11 @@ func Cancel(ctx context.Context, _forge forge.Forge, store store.Store, repo *mo } } - 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 diff --git a/server/pipeline/pipeline_status.go b/server/pipeline/pipeline_status.go index 86bf7183bb8..2ab801b0dbb 100644 --- a/server/pipeline/pipeline_status.go +++ b/server/pipeline/pipeline_status.go @@ -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) diff --git a/server/pipeline/pipeline_status_test.go b/server/pipeline/pipeline_status_test.go index 8964e007a6e..e0a1b0f3a06 100644 --- a/server/pipeline/pipeline_status_test.go +++ b/server/pipeline/pipeline_status_test.go @@ -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) From 71c91b23bedda0c5553c0e5db2a1e88df3652871 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Mon, 2 Mar 2026 12:19:11 +0100 Subject: [PATCH 10/11] fix tc --- web/src/components/repo/pipeline/PipelineLog.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/repo/pipeline/PipelineLog.vue b/web/src/components/repo/pipeline/PipelineLog.vue index d25d4771b91..0b383297c4b 100644 --- a/web/src/components/repo/pipeline/PipelineLog.vue +++ b/web/src/components/repo/pipeline/PipelineLog.vue @@ -175,7 +175,7 @@ const loadedLogs = computed(() => !!log.value); const hasLogs = computed( () => // we do not have logs for skipped/canceled steps - repo?.value && pipeline.value && step.value && (step.value.state !== 'skipped' || step.value.state !== 'canceled'), + 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); From bee7bfe4aaf61f75c5091eb9ebdd71f28359a0e9 Mon Sep 17 00:00:00 2001 From: qwerty287 Date: Mon, 2 Mar 2026 15:08:09 +0100 Subject: [PATCH 11/11] remove todo, change to canceled --- server/rpc/rpc.go | 1 - 1 file changed, 1 deletion(-) diff --git a/server/rpc/rpc.go b/server/rpc/rpc.go index eae0fdb0e27..6680404f2c1 100644 --- a/server/rpc/rpc.go +++ b/server/rpc/rpc.go @@ -559,7 +559,6 @@ func (s *RPC) checkAgentPermissionByWorkflow(_ context.Context, agent *model.Age func (s *RPC) completeChildrenIfParentCompleted(completedWorkflow *model.Workflow) { for _, c := range completedWorkflow.Children { if c.Running() { - // TODO should this be skipped or canceled? 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) }