Skip to content

Commit 5899e3b

Browse files
authored
feat(controlplane): allow filtering workflow runs by workflow and version (#3173)
Signed-off-by: Miguel Martinez Trivino <miguel@chainloop.dev>
1 parent b53424d commit 5899e3b

13 files changed

Lines changed: 227 additions & 38 deletions

app/cli/cmd/workflow_workflow_run_list.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func newWorkflowWorkflowRunListCmd() *cobra.Command {
3333
DefaultLimit: 50,
3434
}
3535

36-
var workflowName, projectName, status, policyStatus string
36+
var workflowName, projectName, projectVersion, status, policyStatus string
3737

3838
cmd := &cobra.Command{
3939
Use: "list",
@@ -48,13 +48,20 @@ func newWorkflowWorkflowRunListCmd() *cobra.Command {
4848
return fmt.Errorf("invalid policy-status %q, please chose one of: all, failed, passed", policyStatus)
4949
}
5050

51+
// A version name is unique only within a project, so filtering by
52+
// version requires the project to be set.
53+
if projectVersion != "" && projectName == "" {
54+
return fmt.Errorf("--project is required when --version is set")
55+
}
56+
5157
return nil
5258
},
5359
RunE: func(cmd *cobra.Command, args []string) error {
5460
res, err := action.NewWorkflowRunList(ActionOpts).Run(
5561
&action.WorkflowRunListOpts{
56-
WorkflowName: workflowName,
57-
ProjectName: projectName,
62+
WorkflowName: workflowName,
63+
ProjectName: projectName,
64+
ProjectVersionName: projectVersion,
5865
Pagination: &action.PaginationOpts{
5966
Limit: paginationOpts.Limit,
6067
NextCursor: paginationOpts.NextCursor,
@@ -88,6 +95,7 @@ func newWorkflowWorkflowRunListCmd() *cobra.Command {
8895

8996
cmd.Flags().StringVar(&workflowName, "workflow", "", "workflow name")
9097
cmd.Flags().StringVar(&projectName, "project", "", "project name")
98+
cmd.Flags().StringVar(&projectVersion, "version", "", "project version name, e.g. v1.2.0 (requires --project)")
9199
cmd.Flags().BoolVar(&full, "full", false, "full report")
92100
cmd.Flags().StringVar(&status, "status", "", fmt.Sprintf("filter by workflow run status: %v", listAvailableWorkflowStatusFlag()))
93101
cmd.Flags().StringVar(&policyStatus, "policy-status", "", "filter by policy violations status: all, failed, passed")
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//
2+
// Copyright 2026 The Chainloop Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package cmd
17+
18+
import (
19+
"testing"
20+
21+
"github.com/stretchr/testify/assert"
22+
"github.com/stretchr/testify/require"
23+
)
24+
25+
func TestWorkflowRunListPreRunValidation(t *testing.T) {
26+
testCases := []struct {
27+
name string
28+
version string
29+
project string
30+
wantErr string
31+
}{
32+
{
33+
name: "version without project is rejected",
34+
version: "v1.0.0",
35+
project: "",
36+
wantErr: "--project is required when --version is set",
37+
},
38+
{
39+
name: "version with project is allowed",
40+
version: "v1.0.0",
41+
project: "my-project",
42+
},
43+
{
44+
name: "no version is allowed without project",
45+
version: "",
46+
project: "",
47+
},
48+
}
49+
50+
for _, tc := range testCases {
51+
t.Run(tc.name, func(t *testing.T) {
52+
cmd := newWorkflowWorkflowRunListCmd()
53+
require.NoError(t, cmd.Flags().Set("version", tc.version))
54+
require.NoError(t, cmd.Flags().Set("project", tc.project))
55+
56+
err := cmd.PreRunE(cmd, nil)
57+
if tc.wantErr != "" {
58+
require.Error(t, err)
59+
assert.Contains(t, err.Error(), tc.wantErr)
60+
return
61+
}
62+
assert.NoError(t, err)
63+
})
64+
}
65+
}

app/cli/documentation/cli-reference.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4034,6 +4034,7 @@ Options
40344034
--policy-status string filter by policy violations status: all, failed, passed
40354035
--project string project name
40364036
--status string filter by workflow run status: [CANCELLED EXPIRED FAILED INITIALIZED SUCCEEDED]
4037+
--version string project version name, e.g. v1.2.0 (requires --project)
40374038
--workflow string workflow name
40384039
```
40394040

app/cli/pkg/action/workflow_run_list.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,12 @@ func NewWorkflowRunList(cfg *ActionsOpts) *WorkflowRunList {
7373

7474
type WorkflowRunListOpts struct {
7575
WorkflowName, ProjectName string
76-
Pagination *PaginationOpts
77-
Status string
78-
PolicyStatus string
76+
// ProjectVersionName filters by project version name (e.g. v1.2.0). It requires
77+
// ProjectName, since a version name is unique only within a project.
78+
ProjectVersionName string
79+
Pagination *PaginationOpts
80+
Status string
81+
PolicyStatus string
7982
}
8083
type PaginationOpts struct {
8184
Limit int
@@ -85,8 +88,9 @@ type PaginationOpts struct {
8588
func (action *WorkflowRunList) Run(opts *WorkflowRunListOpts) (*PaginatedWorkflowRunItem, error) {
8689
client := pb.NewWorkflowRunServiceClient(action.cfg.CPConnection)
8790
req := &pb.WorkflowRunServiceListRequest{
88-
WorkflowName: opts.WorkflowName,
89-
ProjectName: opts.ProjectName,
91+
WorkflowName: opts.WorkflowName,
92+
ProjectName: opts.ProjectName,
93+
ProjectVersionName: opts.ProjectVersionName,
9094
Pagination: &pb.CursorPaginationRequest{
9195
Limit: int32(opts.Pagination.Limit),
9296
Cursor: opts.Pagination.NextCursor,

app/controlplane/api/controlplane/v1/workflow_run.pb.go

Lines changed: 24 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/controlplane/v1/workflow_run.proto

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -215,11 +215,20 @@ message WorkflowRunServiceListRequest {
215215
string project_name = 4;
216216
// by run status
217217
RunStatus status = 3;
218-
// by project version
219-
string project_version = 5 [(buf.validate.field) = {
220-
string: {uuid: true}
221-
ignore: IGNORE_IF_ZERO_VALUE
222-
}];
218+
// by project version (UUID).
219+
// Deprecated: use project_version_name together with project_name. A version
220+
// name is unique only within a project, so filtering by name is the canonical
221+
// and discoverable path; the UUID is kept for backward compatibility.
222+
string project_version = 5 [
223+
deprecated = true,
224+
(buf.validate.field) = {
225+
string: {uuid: true}
226+
ignore: IGNORE_IF_ZERO_VALUE
227+
}
228+
];
229+
// by project version name (e.g. v1.2.0). Requires project_name, since a
230+
// version name is unique only within a project.
231+
string project_version_name = 9;
223232
// by policy violations status
224233
// Deprecated: use policy_status (PolicyStatusFilter), which aligns 1:1 with
225234
// the canonical PolicyStatus enum. When both are set, policy_status wins.
@@ -238,6 +247,14 @@ message WorkflowRunServiceListRequest {
238247
expression: "!(this.workflow_name != '' && this.project_name == '')"
239248
message: "project_name must be set if workflow_name is set"
240249
};
250+
251+
// project_version_name requires project_name (a version name is unique only
252+
// within a project).
253+
option (buf.validate.message).cel = {
254+
id: "list_project_version_name_requires_project_name"
255+
expression: "!(this.project_version_name != '' && this.project_name == '')"
256+
message: "project_name must be set when project_version_name is set"
257+
};
241258
}
242259

243260
message WorkflowRunServiceListResponse {

app/controlplane/api/gen/frontend/controlplane/v1/workflow_run.ts

Lines changed: 27 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunServiceListRequest.jsonschema.json

Lines changed: 10 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/controlplane/api/gen/jsonschema/controlplane.v1.WorkflowRunServiceListRequest.schema.json

Lines changed: 10 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)