Skip to content

Commit 60b0a48

Browse files
vishnu-deepsourcejai-deepsource
authored andcommitted
feat: add filtering options for run issues command
Add support for filtering issues by analyzer, category, severity, code, and path. Filters can be specified multiple times to match any of the provided values. New flags: - --analyzer: filter by analyzer shortcode - --category: filter by issue category - --severity: filter by severity level - --code: filter by issue code - --path: filter by file path (supports partial matching) Also refactor runs command to accept commit-oid argument and wire up issue flags via AddRunIssueFlags for better flag reusability.
1 parent 7004163 commit 60b0a48

3 files changed

Lines changed: 139 additions & 13 deletions

File tree

cli

-18.1 MB
Binary file not shown.

command/runs/issues/issues.go

Lines changed: 129 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,31 @@ import (
2020
)
2121

2222
type IssuesOptions struct {
23-
commitOid string
24-
jsonOutput bool
25-
outputFile string
26-
runWithIssues *runs.RunWithIssues
23+
commitOid string
24+
jsonOutput bool
25+
outputFile string
26+
analyzerFilters []string
27+
categoryFilters []string
28+
severityFilters []string
29+
codeFilters []string
30+
pathFilters []string
31+
runWithIssues *runs.RunWithIssues
32+
}
33+
34+
// AddRunIssueFlags registers flags for filtering and output options.
35+
func AddRunIssueFlags(cmd *cobra.Command, opts *IssuesOptions) {
36+
// --json flag
37+
cmd.Flags().BoolVar(&opts.jsonOutput, "json", false, "Output issues in JSON format")
38+
39+
// --output-file, -o flag
40+
cmd.Flags().StringVarP(&opts.outputFile, "output-file", "o", "", "Output file to write the issues to")
41+
42+
// filter flags
43+
cmd.Flags().StringArrayVar(&opts.analyzerFilters, "analyzer", nil, "Filter issues by analyzer shortcode (repeatable)")
44+
cmd.Flags().StringArrayVar(&opts.categoryFilters, "category", nil, "Filter issues by category (repeatable)")
45+
cmd.Flags().StringArrayVar(&opts.severityFilters, "severity", nil, "Filter issues by severity (repeatable)")
46+
cmd.Flags().StringArrayVar(&opts.codeFilters, "code", nil, "Filter issues by issue code (repeatable)")
47+
cmd.Flags().StringArrayVar(&opts.pathFilters, "path", nil, "Filter issues by path substring (repeatable)")
2748
}
2849

2950
// NewCmdRunsIssues shows the issues in a specific analysis run.
@@ -32,7 +53,8 @@ func NewCmdRunsIssues() *cobra.Command {
3253
View issues in a specific analysis run.
3354
3455
Use %[1]s to view the issues found in a specific run.
35-
`, style.Cyan("deepsource runs issues <commit-oid>"))
56+
Filter issues by analyzer, category, severity, issue code, or path when needed.
57+
`, style.Cyan("deepsource runs <commit-oid>"))
3658

3759
opts := IssuesOptions{}
3860

@@ -47,15 +69,17 @@ func NewCmdRunsIssues() *cobra.Command {
4769
},
4870
}
4971

50-
// --json flag
51-
cmd.Flags().BoolVar(&opts.jsonOutput, "json", false, "Output issues in JSON format")
52-
53-
// --output-file, -o flag
54-
cmd.Flags().StringVarP(&opts.outputFile, "output-file", "o", "", "Output file to write the issues to")
72+
AddRunIssueFlags(cmd, &opts)
5573

5674
return cmd
5775
}
5876

77+
// RunWithCommit runs the command for the provided commit OID.
78+
func (opts *IssuesOptions) RunWithCommit(ctx context.Context, commitOid string) error {
79+
opts.commitOid = commitOid
80+
return opts.Run(ctx)
81+
}
82+
5983
func (opts *IssuesOptions) Run(ctx context.Context) error {
6084
// Load configuration
6185
cfg, err := config.DefaultManager().Load()
@@ -87,6 +111,9 @@ func (opts *IssuesOptions) Run(ctx context.Context) error {
87111
return fmt.Errorf("failed to fetch run issues: %w", err)
88112
}
89113

114+
// Apply filters, if any
115+
runWithIssues.Issues = opts.filterIssues(runWithIssues.Issues)
116+
90117
opts.runWithIssues = runWithIssues
91118

92119
// If JSON output is requested, export and return
@@ -113,7 +140,11 @@ func (opts *IssuesOptions) Run(ctx context.Context) error {
113140

114141
if len(runWithIssues.Issues) == 0 {
115142
pterm.Println("")
116-
pterm.Success.Println("No issues found in this run")
143+
if opts.hasFilters() {
144+
pterm.Success.Println("No issues matched the provided filters")
145+
} else {
146+
pterm.Success.Println("No issues found in this run")
147+
}
117148
return nil
118149
}
119150

@@ -125,6 +156,93 @@ func (opts *IssuesOptions) Run(ctx context.Context) error {
125156
return nil
126157
}
127158

159+
func (opts *IssuesOptions) hasFilters() bool {
160+
return len(opts.analyzerFilters) > 0 ||
161+
len(opts.categoryFilters) > 0 ||
162+
len(opts.severityFilters) > 0 ||
163+
len(opts.codeFilters) > 0 ||
164+
len(opts.pathFilters) > 0
165+
}
166+
167+
func (opts *IssuesOptions) filterIssues(issues []runs.RunIssue) []runs.RunIssue {
168+
if !opts.hasFilters() {
169+
return issues
170+
}
171+
172+
analyzerSet := makeStringSet(opts.analyzerFilters)
173+
categorySet := makeStringSet(opts.categoryFilters)
174+
severitySet := makeStringSet(opts.severityFilters)
175+
codeSet := makeStringSet(opts.codeFilters)
176+
pathFilters := makeLowerStrings(opts.pathFilters)
177+
178+
filtered := make([]runs.RunIssue, 0, len(issues))
179+
for _, issue := range issues {
180+
if len(analyzerSet) > 0 && !setContainsFold(analyzerSet, issue.AnalyzerShortcode) {
181+
continue
182+
}
183+
if len(categorySet) > 0 && !setContainsFold(categorySet, issue.Category) {
184+
continue
185+
}
186+
if len(severitySet) > 0 && !setContainsFold(severitySet, issue.Severity) {
187+
continue
188+
}
189+
if len(codeSet) > 0 && !setContainsFold(codeSet, issue.IssueCode) {
190+
continue
191+
}
192+
if len(pathFilters) > 0 && !matchesPathFilters(issue.Path, pathFilters) {
193+
continue
194+
}
195+
filtered = append(filtered, issue)
196+
}
197+
198+
return filtered
199+
}
200+
201+
func makeStringSet(values []string) map[string]struct{} {
202+
set := make(map[string]struct{})
203+
for _, value := range values {
204+
normalized := strings.ToLower(strings.TrimSpace(value))
205+
if normalized == "" {
206+
continue
207+
}
208+
set[normalized] = struct{}{}
209+
}
210+
return set
211+
}
212+
213+
func makeLowerStrings(values []string) []string {
214+
normalized := make([]string, 0, len(values))
215+
for _, value := range values {
216+
trimmed := strings.TrimSpace(value)
217+
if trimmed == "" {
218+
continue
219+
}
220+
normalized = append(normalized, strings.ToLower(trimmed))
221+
}
222+
return normalized
223+
}
224+
225+
func setContainsFold(set map[string]struct{}, value string) bool {
226+
_, ok := set[strings.ToLower(strings.TrimSpace(value))]
227+
return ok
228+
}
229+
230+
func matchesPathFilters(path string, filters []string) bool {
231+
if path == "" {
232+
return false
233+
}
234+
lowerPath := strings.ToLower(path)
235+
for _, filter := range filters {
236+
if filter == "" {
237+
continue
238+
}
239+
if strings.Contains(lowerPath, filter) {
240+
return true
241+
}
242+
}
243+
return false
244+
}
245+
128246
func (opts *IssuesOptions) showIssues(issues []runs.RunIssue) {
129247
header := []string{"LOCATION", "ANALYZER", "CODE", "TITLE", "CATEGORY", "SEVERITY"}
130248
data := [][]string{header}

command/runs/runs.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,19 @@ type Options struct{}
1212

1313
// NewCmdRuns returns the runs command
1414
func NewCmdRuns() *cobra.Command {
15+
opts := issues.IssuesOptions{}
1516
cmd := &cobra.Command{
16-
Use: "runs",
17+
Use: "runs [commit-oid]",
1718
Short: "View analysis runs for a repository",
19+
Args: cobra.MaximumNArgs(1),
20+
RunE: func(cmd *cobra.Command, args []string) error {
21+
if len(args) == 0 {
22+
return cmd.Help()
23+
}
24+
return opts.RunWithCommit(cmd.Context(), args[0])
25+
},
1826
}
27+
issues.AddRunIssueFlags(cmd, &opts)
1928
cmd.AddCommand(list.NewCmdRunsList())
20-
cmd.AddCommand(issues.NewCmdRunsIssues())
2129
return cmd
2230
}

0 commit comments

Comments
 (0)