diff --git a/cmd/server/openapi/docs.go b/cmd/server/openapi/docs.go index ba2940d67..59e148fc4 100644 --- a/cmd/server/openapi/docs.go +++ b/cmd/server/openapi/docs.go @@ -2966,6 +2966,24 @@ const docTemplate = `{ "description": "only return pipelines after this RFC3339 date", "name": "after", "in": "query" + }, + { + "type": "string", + "description": "filter pipelines by branch", + "name": "branch", + "in": "query" + }, + { + "type": "string", + "description": "filter pipelines by webhook events (comma separated)", + "name": "event", + "in": "query" + }, + { + "type": "string", + "description": "filter pipelines by strings contained in ref", + "name": "ref", + "in": "query" } ], "responses": { diff --git a/server/api/pipeline.go b/server/api/pipeline.go index 2106fe32e..fa9d4ce6c 100644 --- a/server/api/pipeline.go +++ b/server/api/pipeline.go @@ -22,6 +22,7 @@ import ( "fmt" "net/http" "strconv" + "strings" "time" "github.com/gin-gonic/gin" @@ -116,14 +117,32 @@ func createTmpPipeline(event model.WebhookEvent, commit *model.Commit, user *mod // @Param perPage query int false "for response pagination, max items per page" default(50) // @Param before query string false "only return pipelines before this RFC3339 date" // @Param after query string false "only return pipelines after this RFC3339 date" +// @Param branch query string false "filter pipelines by branch" +// @Param event query string false "filter pipelines by webhook events (comma separated)" +// @Param ref query string false "filter pipelines by strings contained in ref" func GetPipelines(c *gin.Context) { repo := session.Repo(c) - before := c.Query("before") - after := c.Query("after") - filter := new(model.PipelineFilter) + filter := &model.PipelineFilter{ + Branch: c.Query("branch"), + RefContains: c.Query("ref"), + } - if before != "" { + if events := c.Query("event"); events != "" { + eventList := strings.Split(events, ",") + wel := make(model.WebhookEventList, 0, len(eventList)) + for _, event := range events { + we := model.WebhookEvent(event) + if err := we.Validate(); err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + wel = append(wel, we) + } + filter.Events = wel + } + + if before := c.Query("before"); before != "" { beforeDt, err := time.Parse(time.RFC3339, before) if err != nil { _ = c.AbortWithError(http.StatusBadRequest, err) @@ -132,7 +151,7 @@ func GetPipelines(c *gin.Context) { filter.Before = beforeDt.Unix() } - if after != "" { + if after := c.Query("after"); after != "" { afterDt, err := time.Parse(time.RFC3339, after) if err != nil { _ = c.AbortWithError(http.StatusBadRequest, err) diff --git a/server/model/pipeline.go b/server/model/pipeline.go index d496b6126..85270ce19 100644 --- a/server/model/pipeline.go +++ b/server/model/pipeline.go @@ -61,8 +61,11 @@ func (Pipeline) TableName() string { } type PipelineFilter struct { - Before int64 - After int64 + Before int64 + After int64 + Branch string + Events []WebhookEvent + RefContains string } // IsMultiPipeline checks if step list contain more than one parent step. diff --git a/server/store/datastore/helper.go b/server/store/datastore/helper.go index a883cf516..4ba698b88 100644 --- a/server/store/datastore/helper.go +++ b/server/store/datastore/helper.go @@ -52,7 +52,7 @@ func wrapDelete(c int64, err error) error { } func (s storage) paginate(p *model.ListOptions) *xorm.Session { - if p.All { + if p == nil || p.All { return s.engine.NewSession() } if p.PerPage < 1 { diff --git a/server/store/datastore/pipeline.go b/server/store/datastore/pipeline.go index 41cc1e780..33a68e5bb 100644 --- a/server/store/datastore/pipeline.go +++ b/server/store/datastore/pipeline.go @@ -65,6 +65,18 @@ func (s storage) GetPipelineList(repo *model.Repo, p *model.ListOptions, f *mode if f.Before != 0 { cond = cond.And(builder.Lt{"created": f.Before}) } + + if f.Branch != "" { + cond = cond.And(builder.Eq{"branch": f.Branch}) + } + + if len(f.Events) != 0 { + cond = cond.And(builder.In("event", f.Events)) + } + + if f.RefContains != "" { + cond = cond.And(builder.Like{"ref", f.RefContains}) + } } return pipelines, s.paginate(p).Where(cond). diff --git a/server/store/datastore/pipeline_test.go b/server/store/datastore/pipeline_test.go index 091e44720..fa18a085d 100644 --- a/server/store/datastore/pipeline_test.go +++ b/server/store/datastore/pipeline_test.go @@ -213,21 +213,49 @@ func TestPipelines(t *testing.T) { pipeline1 := &model.Pipeline{ RepoID: repo.ID, Status: model.StatusFailure, + Event: model.EventCron, + Ref: "refs/heads/some-branch", + Branch: "some-branch", } pipeline2 := &model.Pipeline{ RepoID: repo.ID, Status: model.StatusSuccess, + Event: model.EventPull, + Ref: "refs/pull/32", + Branch: "main", } - err1 := store.CreatePipeline(pipeline1, []*model.Step{}...) - g.Assert(err1).IsNil() - err2 := store.CreatePipeline(pipeline2, []*model.Step{}...) - g.Assert(err2).IsNil() - pipelines, err3 := store.GetPipelineList(&model.Repo{ID: 1}, &model.ListOptions{Page: 1, PerPage: 50}, nil) - g.Assert(err3).IsNil() + err := store.CreatePipeline(pipeline1, []*model.Step{}...) + g.Assert(err).IsNil() + err = store.CreatePipeline(pipeline2, []*model.Step{}...) + g.Assert(err).IsNil() + pipelines, err := store.GetPipelineList(&model.Repo{ID: 1}, &model.ListOptions{Page: 1, PerPage: 50}, nil) + g.Assert(err).IsNil() g.Assert(len(pipelines)).Equal(2) g.Assert(pipelines[0].ID).Equal(pipeline2.ID) g.Assert(pipelines[0].RepoID).Equal(pipeline2.RepoID) g.Assert(pipelines[0].Status).Equal(pipeline2.Status) + + pipelines, err = store.GetPipelineList(&model.Repo{ID: 1}, nil, &model.PipelineFilter{ + Branch: "main", + }) + g.Assert(err).IsNil() + g.Assert(len(pipelines)).Equal(1) + g.Assert(pipelines[0].ID).Equal(pipeline2.ID) + + pipelines, err = store.GetPipelineList(&model.Repo{ID: 1}, nil, &model.PipelineFilter{ + Events: []model.WebhookEvent{model.EventCron}, + }) + g.Assert(err).IsNil() + g.Assert(len(pipelines)).Equal(1) + g.Assert(pipelines[0].ID).Equal(pipeline1.ID) + + pipelines, err = store.GetPipelineList(&model.Repo{ID: 1}, nil, &model.PipelineFilter{ + Events: []model.WebhookEvent{model.EventCron, model.EventPull}, + RefContains: "32", + }) + g.Assert(err).IsNil() + g.Assert(len(pipelines)).Equal(1) + g.Assert(pipelines[0].ID).Equal(pipeline2.ID) }) g.It("Should get filtered pipelines", func() { diff --git a/web/src/lib/api/index.ts b/web/src/lib/api/index.ts index a70741901..d355ba0ee 100644 --- a/web/src/lib/api/index.ts +++ b/web/src/lib/api/index.ts @@ -105,7 +105,7 @@ export default class WoodpeckerClient extends ApiClient { async getPipelineList( repoId: number, - opts?: PaginationOptions & { before?: string; after?: string }, + opts?: PaginationOptions & { before?: string; after?: string; ref?: string; branch?: string; events?: string }, ): Promise { const query = encodeQueryString(opts); return this._get(`/api/repos/${repoId}/pipelines?${query}`) as Promise;