Skip to content

Commit 3d45d5d

Browse files
branczclaudepre-commit-ci-lite[bot]
authored
Add starts with filter options (#6062)
* Add "starts with" and "does not start with" filters for stacks/frames This commit adds two new string filter types to match "contains" and "does not contain": - starts_with: matches strings that start with the specified prefix - not_starts_with: matches strings that do not start with the specified prefix Changes: - proto: Added starts_with and not_starts_with fields to StringCondition message - backend: Updated matchesStringCondition() to handle new filter types using bytes.HasPrefix - frontend UI: Added "Starts With" and "Not Starts With" options to filter dropdown - frontend types: Updated ProfileFilter interface and createStringCondition() for new match types - tests: Added 4 comprehensive tests covering stack and frame filters with both new filter types The implementation follows the same pattern as existing contains/not_contains filters, with case-insensitive matching and proper null handling. * [pre-commit.ci lite] apply automatic fixes * Fix: Add NotStartsWith to negative condition checks The NotStartsWith filter was being treated as a positive condition, causing incorrect filter logic. Stack filters with negative conditions need ALL functions to not match (AND logic), while positive conditions need ANY function to match (OR logic). Fixed in 4 functions: - handleUnsymbolizedFunctionCondition - matchesFunctionNameInRange - matchesSystemNameInRange - matchesFilenameInRange This fixes the failing TestStackFilterFunctionNameNotStartsWith test. * Fix test compatibility with NewRecordReader API change NewRecordReader now returns (reader, error) instead of just reader. Updated all 4 test cases to handle the error return value. --------- Co-authored-by: Claude <[email protected]> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent 801f32b commit 3d45d5d

File tree

10 files changed

+532
-13
lines changed

10 files changed

+532
-13
lines changed

gen/proto/go/parca/query/v1alpha1/query.pb.go

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

gen/proto/go/parca/query/v1alpha1/query_vtproto.pb.go

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

gen/proto/swagger/parca/query/v1alpha1/query.swagger.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1763,6 +1763,14 @@
17631763
"notContains": {
17641764
"type": "string",
17651765
"title": "not_contains matches strings that do not contain the specified substring"
1766+
},
1767+
"startsWith": {
1768+
"type": "string",
1769+
"title": "starts_with matches strings that start with the specified prefix"
1770+
},
1771+
"notStartsWith": {
1772+
"type": "string",
1773+
"title": "not_starts_with matches strings that do not start with the specified prefix"
17661774
}
17671775
},
17681776
"title": "StringCondition defines string-based filtering conditions"

gen/proto/swagger/parca/share/v1alpha1/share.swagger.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,14 @@
808808
"notContains": {
809809
"type": "string",
810810
"title": "not_contains matches strings that do not contain the specified substring"
811+
},
812+
"startsWith": {
813+
"type": "string",
814+
"title": "starts_with matches strings that start with the specified prefix"
815+
},
816+
"notStartsWith": {
817+
"type": "string",
818+
"title": "not_starts_with matches strings that do not start with the specified prefix"
811819
}
812820
},
813821
"title": "StringCondition defines string-based filtering conditions"

pkg/query/columnquery.go

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -715,22 +715,22 @@ func stackMatchesFilter(
715715

716716
func handleUnsymbolizedFunctionCondition(fnCond *pb.StringCondition) bool {
717717
switch fnCond.GetCondition().(type) {
718-
case *pb.StringCondition_Contains, *pb.StringCondition_Equal:
719-
// For Contains/Equal: no function names means no matches
718+
case *pb.StringCondition_Contains, *pb.StringCondition_Equal, *pb.StringCondition_StartsWith:
719+
// For Contains/Equal/StartsWith: no function names means no matches
720720
return false
721-
case *pb.StringCondition_NotContains, *pb.StringCondition_NotEqual:
722-
// For NotContains/NotEqual: no function names means condition is satisfied
721+
case *pb.StringCondition_NotContains, *pb.StringCondition_NotEqual, *pb.StringCondition_NotStartsWith:
722+
// For NotContains/NotEqual/NotStartsWith: no function names means condition is satisfied
723723
return true
724724
}
725725
return false
726726
}
727727

728728
func matchesFunctionNameInRange(r *profile.RecordReader, firstStart, lastEnd int, fnCond *pb.StringCondition) bool {
729-
// For NotContains/NotEqual, we need ALL functions to not contain/equal the target (AND logic)
730-
// For Contains/Equal, we need ANY function to contain/equal the target (OR logic)
729+
// For NotContains/NotEqual/NotStartsWith, we need ALL functions to not contain/equal/start-with the target (AND logic)
730+
// For Contains/Equal/StartsWith, we need ANY function to contain/equal/start-with the target (OR logic)
731731
isNegativeCondition := false
732732
switch fnCond.GetCondition().(type) {
733-
case *pb.StringCondition_NotContains, *pb.StringCondition_NotEqual:
733+
case *pb.StringCondition_NotContains, *pb.StringCondition_NotEqual, *pb.StringCondition_NotStartsWith:
734734
isNegativeCondition = true
735735
}
736736

@@ -771,7 +771,7 @@ func matchesFunctionNameInRange(r *profile.RecordReader, firstStart, lastEnd int
771771
func matchesSystemNameInRange(r *profile.RecordReader, firstStart, lastEnd int, sysCond *pb.StringCondition) bool {
772772
isNegativeCondition := false
773773
switch sysCond.GetCondition().(type) {
774-
case *pb.StringCondition_NotContains, *pb.StringCondition_NotEqual:
774+
case *pb.StringCondition_NotContains, *pb.StringCondition_NotEqual, *pb.StringCondition_NotStartsWith:
775775
isNegativeCondition = true
776776
}
777777

@@ -802,7 +802,7 @@ func matchesSystemNameInRange(r *profile.RecordReader, firstStart, lastEnd int,
802802
func matchesFilenameInRange(r *profile.RecordReader, firstStart, lastEnd int, fileCond *pb.StringCondition) bool {
803803
isNegativeCondition := false
804804
switch fileCond.GetCondition().(type) {
805-
case *pb.StringCondition_NotContains, *pb.StringCondition_NotEqual:
805+
case *pb.StringCondition_NotContains, *pb.StringCondition_NotEqual, *pb.StringCondition_NotStartsWith:
806806
isNegativeCondition = true
807807
}
808808

@@ -1003,6 +1003,12 @@ func matchesStringCondition(value []byte, condition *pb.StringCondition) bool {
10031003
case *pb.StringCondition_NotContains:
10041004
target := bytes.ToLower([]byte(condition.GetNotContains()))
10051005
return !bytes.Contains(valueLower, target)
1006+
case *pb.StringCondition_StartsWith:
1007+
target := bytes.ToLower([]byte(condition.GetStartsWith()))
1008+
return bytes.HasPrefix(valueLower, target)
1009+
case *pb.StringCondition_NotStartsWith:
1010+
target := bytes.ToLower([]byte(condition.GetNotStartsWith()))
1011+
return !bytes.HasPrefix(valueLower, target)
10061012
default:
10071013
return true
10081014
}

0 commit comments

Comments
 (0)