Insights: support filtering insightViews graphql query based on a search context embedded query pattern (#33686)

This commit is contained in:
coury-clark 2022-04-12 14:46:11 -07:00 committed by GitHub
parent ca8deb2895
commit 1c290f11a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 116 additions and 11 deletions

View File

@ -337,6 +337,7 @@ type InsightViewControlsInput struct {
type InsightViewFiltersInput struct {
IncludeRepoRegex *string
ExcludeRepoRegex *string
SearchContexts *[]string
}
type LineChartSearchInsightDataSeriesInput struct {

View File

@ -622,6 +622,11 @@ input InsightViewFiltersInput {
A regex string for which to exclude repositories in a filter.
"""
excludeRepoRegex: String
"""
A list of query based search contexts to include in the filters for the view.
"""
searchContexts: [String!]
}
"""

View File

@ -4,6 +4,16 @@ import (
"context"
"time"
"github.com/sourcegraph/sourcegraph/internal/database/dbutil"
sctypes "github.com/sourcegraph/sourcegraph/internal/types"
"github.com/sourcegraph/sourcegraph/internal/search/searchcontexts"
"github.com/sourcegraph/sourcegraph/lib/errors"
"github.com/sourcegraph/sourcegraph/internal/database"
"github.com/sourcegraph/sourcegraph/enterprise/internal/insights/timeseries"
"github.com/sourcegraph/sourcegraph/enterprise/internal/insights/query"
@ -14,6 +24,8 @@ import (
"github.com/sourcegraph/sourcegraph/enterprise/internal/insights/background/queryrunner"
"github.com/sourcegraph/sourcegraph/enterprise/internal/insights/store"
"github.com/sourcegraph/sourcegraph/internal/database/basestore"
searchquery "github.com/sourcegraph/sourcegraph/internal/search/query"
)
var _ graphqlbackend.InsightSeriesResolver = &insightSeriesResolver{}
@ -60,21 +72,36 @@ func (r *insightSeriesResolver) Points(ctx context.Context, args *graphqlbackend
opts.To = &args.To.Time
}
includeRepo := func(regex ...string) {
opts.IncludeRepoRegex = append(opts.IncludeRepoRegex, regex...)
}
excludeRepo := func(regex ...string) {
opts.ExcludeRepoRegex = append(opts.ExcludeRepoRegex, regex...)
}
// to preserve backwards compatibility, we are going to keep the arguments on this resolver for now. Ideally
// we would deprecate these in favor of passing arguments from a higher level resolver (insight view) to match
// the model of how we want default filters to work at the insight view level. That said, we will only inherit
// higher resolver filters if provided filter arguments are nil.
if args.IncludeRepoRegex != nil {
opts.IncludeRepoRegex = *args.IncludeRepoRegex
includeRepo(*args.IncludeRepoRegex)
} else if r.filters.IncludeRepoRegex != nil {
opts.IncludeRepoRegex = *r.filters.IncludeRepoRegex
includeRepo(*r.filters.IncludeRepoRegex)
}
if args.ExcludeRepoRegex != nil {
opts.ExcludeRepoRegex = *args.ExcludeRepoRegex
excludeRepo(*args.ExcludeRepoRegex)
} else if r.filters.ExcludeRepoRegex != nil {
opts.ExcludeRepoRegex = *r.filters.ExcludeRepoRegex
excludeRepo(*r.filters.ExcludeRepoRegex)
}
scLoader := &scLoader{primary: r.workerBaseStore.Handle().DB()}
inc, exc, err := unwrapSearchContexts(ctx, scLoader, r.filters.SearchContexts)
if err != nil {
return nil, errors.Wrap(err, "unwrapSearchContexts")
}
includeRepo(inc...)
excludeRepo(exc...)
points, err := r.insightsStore.SeriesPoints(ctx, opts)
if err != nil {
return nil, err
@ -86,6 +113,47 @@ func (r *insightSeriesResolver) Points(ctx context.Context, args *graphqlbackend
return resolvers, nil
}
// SearchContextLoader loads search contexts just from the full name of the
// context. This will not verify that the calling context owns the context, it
// will load regardless of the current user.
type SearchContextLoader interface {
GetByName(ctx context.Context, name string) (*sctypes.SearchContext, error)
}
type scLoader struct {
primary dbutil.DB
}
func (l *scLoader) GetByName(ctx context.Context, name string) (*sctypes.SearchContext, error) {
db := database.NewDB(l.primary)
return searchcontexts.ResolveSearchContextSpec(ctx, db, name)
}
func unwrapSearchContexts(ctx context.Context, loader SearchContextLoader, rawContexts []string) ([]string, []string, error) {
var include []string
var exclude []string
for _, rawContext := range rawContexts {
searchContext, err := loader.GetByName(ctx, rawContext)
if err != nil {
return nil, nil, err
}
if searchContext.Query != "" {
var plan searchquery.Plan
plan, err := searchquery.Pipeline(
searchquery.Init(searchContext.Query, searchquery.SearchTypeRegex),
)
if err != nil {
return nil, nil, errors.Wrapf(err, "failed to parse search query for search context: %s", rawContext)
}
inc, exc := plan.ToParseTree().Repositories()
include = append(include, inc...)
exclude = append(exclude, exc...)
}
}
return include, exclude, nil
}
func (r *insightSeriesResolver) Status(ctx context.Context) (graphqlbackend.InsightStatusResolver, error) {
seriesID := r.series.SeriesID

View File

@ -101,7 +101,7 @@ func TestResolver_InsightSeries(t *testing.T) {
if err != nil {
t.Fatal(err)
}
autogold.Want("insights[0][0].Points store opts", `{"SeriesID":"1234567","RepoID":null,"Excluded":null,"Included":null,"IncludeRepoRegex":"","ExcludeRepoRegex":"","From":"2006-01-02T15:04:05Z","To":"2006-01-03T15:04:05Z","Limit":0}`).Equal(t, string(json))
autogold.Want("insights[0][0].Points store opts", `{"SeriesID":"1234567","RepoID":null,"Excluded":null,"Included":null,"IncludeRepoRegex":null,"ExcludeRepoRegex":null,"From":"2006-01-02T15:04:05Z","To":"2006-01-03T15:04:05Z","Limit":0}`).Equal(t, string(json))
return []store.SeriesPoint{
{Time: args.From.Time, Value: 1},
{Time: args.From.Time, Value: 2},

View File

@ -175,12 +175,27 @@ func expandCaptureGroupSeriesRecorded(ctx context.Context, definition types.Insi
}
opts.From = &oldest
includeRepo := func(regex ...string) {
opts.IncludeRepoRegex = append(opts.IncludeRepoRegex, regex...)
}
excludeRepo := func(regex ...string) {
opts.ExcludeRepoRegex = append(opts.ExcludeRepoRegex, regex...)
}
if filters.IncludeRepoRegex != nil {
opts.IncludeRepoRegex = *filters.IncludeRepoRegex
includeRepo(*filters.IncludeRepoRegex)
}
if filters.ExcludeRepoRegex != nil {
opts.ExcludeRepoRegex = *filters.ExcludeRepoRegex
excludeRepo(*filters.ExcludeRepoRegex)
}
scLoader := &scLoader{primary: r.workerBaseStore.Handle().DB()}
inc, exc, err := unwrapSearchContexts(ctx, scLoader, filters.SearchContexts)
if err != nil {
return nil, errors.Wrap(err, "unwrapSearchContexts")
}
includeRepo(inc...)
excludeRepo(exc...)
groupedByCapture := make(map[string][]store.SeriesPoint)
allPoints, err := r.timeSeriesStore.SeriesPoints(ctx, opts)
if err != nil {
@ -797,6 +812,7 @@ type InsightViewQueryConnectionResolver struct {
func (d *InsightViewQueryConnectionResolver) Nodes(ctx context.Context) ([]graphqlbackend.InsightViewResolver, error) {
resolvers := make([]graphqlbackend.InsightViewResolver, 0)
var scs []string
views, _, err := d.computeViews(ctx)
if err != nil {
@ -805,9 +821,13 @@ func (d *InsightViewQueryConnectionResolver) Nodes(ctx context.Context) ([]graph
for i := range views {
resolver := &insightViewResolver{view: &views[i], baseInsightResolver: d.baseInsightResolver}
if d.args.Filters != nil {
if d.args.Filters.SearchContexts != nil {
scs = *d.args.Filters.SearchContexts
}
resolver.overrideFilters = &types.InsightViewFilters{
IncludeRepoRegex: d.args.Filters.IncludeRepoRegex,
ExcludeRepoRegex: d.args.Filters.ExcludeRepoRegex,
SearchContexts: scs,
}
}
resolvers = append(resolvers, resolver)

View File

@ -107,8 +107,8 @@ type SeriesPointsOpts struct {
// TODO(slimsag): Add ability to filter based on repo name, original name.
// TODO(slimsag): Add ability to do limited filtering based on metadata.
IncludeRepoRegex string
ExcludeRepoRegex string
IncludeRepoRegex []string
ExcludeRepoRegex []string
// Time ranges to query from/to, if non-nil, in UTC.
From, To *time.Time
@ -246,10 +246,20 @@ func seriesPointsQuery(opts SeriesPointsOpts) *sqlf.Query {
preds = append(preds, sqlf.Sprintf(s))
}
if len(opts.IncludeRepoRegex) > 0 {
preds = append(preds, sqlf.Sprintf("rn.name ~ %s", opts.IncludeRepoRegex))
for _, regex := range opts.IncludeRepoRegex {
if len(regex) == 0 {
continue
}
preds = append(preds, sqlf.Sprintf("rn.name ~ %s", regex))
}
}
if len(opts.ExcludeRepoRegex) > 0 {
preds = append(preds, sqlf.Sprintf("rn.name !~ %s", opts.ExcludeRepoRegex))
for _, regex := range opts.ExcludeRepoRegex {
if len(regex) == 0 {
continue
}
preds = append(preds, sqlf.Sprintf("rn.name !~ %s", regex))
}
}
if len(preds) == 0 {

View File

@ -51,6 +51,7 @@ type Insight struct {
type InsightViewFilters struct {
IncludeRepoRegex *string
ExcludeRepoRegex *string
SearchContexts []string
}
// InsightViewSeriesMetadata contains metadata about a viewable insight series such as render properties.