Monorepo for Tangled
0

Configure Feed

Select the types of activity you want to include in your feed.

appview/pipelines,spindle: filter pipelines list by trigger kind

Signed-off-by: dawn <dawn@tangled.org>

dawn (Jul 14, 2026, 12:57 AM +0300) 4d884e3a a00289e3

+151 -18
+5 -1
api/tangled/ciqueryPipelines.go
··· 26 26 // 27 27 // commits: Filter pipelines by commits. When provided, maximum one pipeline per commit id will be returned. 28 28 // cursor: Pagination cursor 29 + // kinds: Filter pipelines by trigger kind. When provided, pipelines matching any listed kind are returned; when omitted, every kind is returned. 29 30 // limit: Maximum number of pipelines to return 30 31 // repo: DID of the repository 31 - func CiQueryPipelines(ctx context.Context, c util.LexClient, commits []string, cursor string, limit int64, repo string) (*CiQueryPipelines_Output, error) { 32 + func CiQueryPipelines(ctx context.Context, c util.LexClient, commits []string, cursor string, kinds []string, limit int64, repo string) (*CiQueryPipelines_Output, error) { 32 33 var out CiQueryPipelines_Output 33 34 34 35 params := map[string]interface{}{} ··· 37 38 } 38 39 if cursor != "" { 39 40 params["cursor"] = cursor 41 + } 42 + if len(kinds) != 0 { 43 + params["kinds"] = kinds 40 44 } 41 45 if limit != 0 { 42 46 params["limit"] = limit
+4 -11
appview/pipelines/pipelines.go
··· 19 19 "tangled.org/core/hostutil" 20 20 "tangled.org/core/idresolver" 21 21 "tangled.org/core/lexutil" 22 - "tangled.org/core/orm" 23 22 "tangled.org/core/rbac" 24 23 "tangled.org/core/types" 25 24 ··· 88 87 } 89 88 90 89 filterKind := r.URL.Query().Get("trigger") 91 - filters := []orm.Filter{ 92 - orm.FilterEq("p.repo_did", f.RepoDid), 93 - } 90 + var kinds []string 94 91 switch filterKind { 95 - case "push": 96 - filters = append(filters, orm.FilterEq("t.kind", "push")) 97 - case "pull_request": 98 - filters = append(filters, orm.FilterEq("t.kind", "pull_request")) 92 + case "push", "pull_request": 93 + kinds = []string{filterKind} 99 94 default: 100 - // no filters otherwise, default to "all" 101 95 filterKind = "all" 102 96 } 103 97 ··· 125 119 return 126 120 } 127 121 128 - // sh.tangled.ci.queryPipelines(repo, kind, limit=30) 129 122 xrpcc := indigoxrpc.Client{Host: spindleUrl} 130 - out, err := tangled.CiQueryPipelines(r.Context(), &xrpcc, nil, "", 30, f.RepoDid) 123 + out, err := tangled.CiQueryPipelines(r.Context(), &xrpcc, nil, "", kinds, 30, f.RepoDid) 131 124 if err != nil { 132 125 l.Error("failed to fetch pipelines", "err", err) 133 126 p.pages.Pipelines(w, pages.PipelinesParams{
+1 -1
appview/pipelines/ssh/session.go
··· 53 53 } 54 54 55 55 xrpcc := extlexutil.Client{Client: indigoxrpc.Client{Host: host}} 56 - out, err := tangled.CiQueryPipelines(sess.Context(), &xrpcc, []string{sha}, "", 1, repoDID) 56 + out, err := tangled.CiQueryPipelines(sess.Context(), &xrpcc, []string{sha}, "", nil, 1, repoDID) 57 57 if err != nil || len(out.Pipelines) == 0 { 58 58 l.Warn("pipeline not found", "err", err) 59 59 return newErrorModel(renderer, fmt.Sprintf("pipeline not found for repo %s @ %s", repoDID, sha)), wishtea.MakeOptions(sess)
+1 -1
appview/pulls/list.go
··· 276 276 return m 277 277 } 278 278 xrpcc := &indigoxrpc.Client{Host: spindleUrl} 279 - out, err := tangled.CiQueryPipelines(ctx, xrpcc, shas, "", 0, f.RepoDid) 279 + out, err := tangled.CiQueryPipelines(ctx, xrpcc, shas, "", nil, 0, f.RepoDid) 280 280 if err != nil { 281 281 l.Error("failed to fetch pipelines", "err", err) 282 282 return m
+1 -1
appview/pulls/single.go
··· 172 172 return m 173 173 } 174 174 xrpcc := &indigoxrpc.Client{Host: spindleUrl} 175 - out, err := tangled.CiQueryPipelines(ctx, xrpcc, shas, "", 0, f.RepoDid) 175 + out, err := tangled.CiQueryPipelines(ctx, xrpcc, shas, "", nil, 0, f.RepoDid) 176 176 if err != nil { 177 177 l.Error("failed to fetch pipelines", "err", err) 178 178 return m
+1 -1
appview/repo/repo_util.go
··· 110 110 } 111 111 112 112 xrpcc := &indigoxrpc.Client{Host: spindleUrl} 113 - out, err := tangled.CiQueryPipelines(ctx, xrpcc, shas, "", 0, repo.RepoDid) 113 + out, err := tangled.CiQueryPipelines(ctx, xrpcc, shas, "", nil, 0, repo.RepoDid) 114 114 if err != nil { 115 115 return nil, err 116 116 }
+8
lexicons/ci/queryPipelines.json
··· 22 22 }, 23 23 "description": "Filter pipelines by commits. When provided, maximum one pipeline per commit id will be returned." 24 24 }, 25 + "kinds": { 26 + "type": "array", 27 + "items": { 28 + "type": "string", 29 + "enum": ["push", "pull_request", "manual"] 30 + }, 31 + "description": "Filter pipelines by trigger kind. When provided, pipelines matching any listed kind are returned; when omitted, every kind is returned." 32 + }, 25 33 "limit": { 26 34 "type": "integer", 27 35 "description": "Maximum number of pipelines to return",
+10 -1
spindle/db/pipelines.go
··· 12 12 "tangled.org/core/workflow" 13 13 ) 14 14 15 - func (d *DB) QueryPipelines(ctx context.Context, repoDid string, commits []string, cursor string, limit int) ([]*tangled.CiPipeline, string, int64, error) { 15 + func (d *DB) QueryPipelines(ctx context.Context, repoDid string, commits []string, cursor string, kinds []string, limit int) ([]*tangled.CiPipeline, string, int64, error) { 16 16 if limit <= 0 { 17 17 limit = 30 18 18 } ··· 39 39 json_extract(event, '$.triggerMetadata.pullRequest.sourceSha'), 40 40 json_extract(event, '$.triggerMetadata.manual.sha') 41 41 ) in (` + strings.Join(placeholders, ",") + ")" 42 + } 43 + 44 + if len(kinds) > 0 { 45 + placeholders := make([]string, len(kinds)) 46 + for i := range kinds { 47 + placeholders[i] = "?" 48 + args = append(args, kinds[i]) 49 + } 50 + query += " and json_extract(event, '$.triggerMetadata.kind') in (" + strings.Join(placeholders, ",") + ")" 42 51 } 43 52 44 53 if cursor != "" {
+118
spindle/db/pipelines_test.go
··· 1 + package db 2 + 3 + import ( 4 + "context" 5 + "encoding/json" 6 + "slices" 7 + "testing" 8 + "time" 9 + 10 + "tangled.org/core/api/tangled" 11 + ) 12 + 13 + func seedPipelineEvent(t *testing.T, d *DB, rkey, repoDid, kind string, created int64) { 14 + t.Helper() 15 + repo := repoDid 16 + tm := &tangled.Pipeline_TriggerMetadata{ 17 + Kind: kind, 18 + Repo: &tangled.Pipeline_TriggerRepo{Knot: "knot.test", RepoDid: &repo, Did: repoDid}, 19 + } 20 + switch kind { 21 + case "push": 22 + tm.Push = &tangled.Pipeline_PushTriggerData{NewSha: "sha-" + rkey, Ref: "refs/heads/main"} 23 + case "pull_request": 24 + tm.PullRequest = &tangled.Pipeline_PullRequestTriggerData{SourceSha: "sha-" + rkey, SourceBranch: "feature", TargetBranch: "main"} 25 + case "manual": 26 + tm.Manual = &tangled.Pipeline_ManualTriggerData{Sha: "sha-" + rkey} 27 + } 28 + raw := tangled.Pipeline{ 29 + TriggerMetadata: tm, 30 + Workflows: []*tangled.Pipeline_Workflow{{Name: "ci.yml"}}, 31 + } 32 + eventJson, err := json.Marshal(raw) 33 + if err != nil { 34 + t.Fatalf("marshal pipeline: %v", err) 35 + } 36 + if _, err := d.Exec( 37 + `insert into events (rkey, nsid, event, created) values (?, 'sh.tangled.pipeline', ?, ?)`, 38 + rkey, string(eventJson), created, 39 + ); err != nil { 40 + t.Fatalf("seed event %s: %v", rkey, err) 41 + } 42 + } 43 + 44 + func TestQueryPipelines_FilterByKind(t *testing.T) { 45 + d := newTestDB(t) 46 + ctx := context.Background() 47 + repo := "did:plc:boltless" 48 + base := time.Now().UnixNano() 49 + 50 + seedPipelineEvent(t, d, "p-push", repo, "push", base+1) 51 + seedPipelineEvent(t, d, "p-pull", repo, "pull_request", base+2) 52 + seedPipelineEvent(t, d, "p-manual", repo, "manual", base+3) 53 + 54 + cases := []struct { 55 + kinds []string 56 + wantTotal int64 57 + wantKinds []string 58 + }{ 59 + {nil, 3, []string{"manual", "pull_request", "push"}}, 60 + {[]string{"push"}, 1, []string{"push"}}, 61 + {[]string{"pull_request"}, 1, []string{"pull_request"}}, 62 + {[]string{"manual"}, 1, []string{"manual"}}, 63 + {[]string{"push", "pull_request"}, 2, []string{"pull_request", "push"}}, 64 + } 65 + 66 + for _, tc := range cases { 67 + pipelines, _, total, err := d.QueryPipelines(ctx, repo, nil, "", tc.kinds, 30) 68 + if err != nil { 69 + t.Fatalf("kinds=%v: QueryPipelines: %v", tc.kinds, err) 70 + } 71 + if total != tc.wantTotal { 72 + t.Errorf("kinds=%v: total = %d, want %d", tc.kinds, total, tc.wantTotal) 73 + } 74 + var gotKinds []string 75 + for _, p := range pipelines { 76 + gotKinds = append(gotKinds, triggerKindOf(p)) 77 + } 78 + slices.Sort(gotKinds) 79 + if !slices.Equal(gotKinds, tc.wantKinds) { 80 + t.Errorf("kinds=%v: returned %v, want %v", tc.kinds, gotKinds, tc.wantKinds) 81 + } 82 + } 83 + } 84 + 85 + func TestQueryPipelines_KindScopedToRepo(t *testing.T) { 86 + d := newTestDB(t) 87 + ctx := context.Background() 88 + base := time.Now().UnixNano() 89 + 90 + seedPipelineEvent(t, d, "a-push", "did:plc:alice", "push", base+1) 91 + seedPipelineEvent(t, d, "b-push", "did:plc:bob", "push", base+2) 92 + 93 + pipelines, _, total, err := d.QueryPipelines(ctx, "did:plc:alice", nil, "", []string{"push"}, 30) 94 + if err != nil { 95 + t.Fatalf("QueryPipelines: %v", err) 96 + } 97 + if total != 1 || len(pipelines) != 1 { 98 + t.Fatalf("total=%d len=%d, want exactly alice's single push pipeline", total, len(pipelines)) 99 + } 100 + if pipelines[0].Repo == nil || *pipelines[0].Repo != "did:plc:alice" { 101 + t.Errorf("returned pipeline repo = %v, want did:plc:alice", pipelines[0].Repo) 102 + } 103 + } 104 + 105 + func triggerKindOf(p *tangled.CiPipeline) string { 106 + if p.Trigger == nil { 107 + return "" 108 + } 109 + switch { 110 + case p.Trigger.CiTrigger_Push != nil: 111 + return "push" 112 + case p.Trigger.CiTrigger_PullRequest != nil: 113 + return "pull_request" 114 + case p.Trigger.CiTrigger_Manual != nil: 115 + return "manual" 116 + } 117 + return "" 118 + }
+2 -1
spindle/xrpc/ci_query_pipelines.go
··· 24 24 25 25 commits := r.URL.Query()["commits"] 26 26 cursor := r.URL.Query().Get("cursor") 27 + kinds := r.URL.Query()["kinds"] 27 28 limitStr := r.URL.Query().Get("limit") 28 29 29 30 limit := 30 ··· 33 34 } 34 35 } 35 36 36 - pipelines, nextCursor, total, err := x.Db.QueryPipelines(r.Context(), repo, commits, cursor, limit) 37 + pipelines, nextCursor, total, err := x.Db.QueryPipelines(r.Context(), repo, commits, cursor, kinds, limit) 37 38 if err != nil { 38 39 fail(xrpcerr.GenericError(err), http.StatusInternalServerError) 39 40 return