Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Filter searchSchema results based on current search query params #334

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 39 additions & 6 deletions graph/generated/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions graph/schema.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ type Query {
searchComplete(property: String!, query: SearchInput, limit: Int): [String]

"""
Returns all properties from resources currently in the index.
Returns all fields from resources currently in the index.
Optionally, a query can be included to filter the results.
For example, if we want to only get fields for Pod resources, we can pass in a query with the filter `{property: kind, values:['Pod']}`
"""
searchSchema: Map
searchSchema(query: SearchInput): Map

"""
Additional information about the service status or conditions found while processing the query.
Expand Down
4 changes: 2 additions & 2 deletions graph/schema.resolvers.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 24 additions & 6 deletions pkg/resolver/searchSchema.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/doug-martin/goqu/v9"
"github.com/doug-martin/goqu/v9/exp"
"github.com/driftprogramming/pgxpoolmock"
"github.com/stolostron/search-v2-api/graph/model"
"github.com/stolostron/search-v2-api/pkg/config"
db "github.com/stolostron/search-v2-api/pkg/database"
"github.com/stolostron/search-v2-api/pkg/rbac"
Expand All @@ -17,17 +18,28 @@ type SearchSchema struct {
query string
params []interface{}
userData rbac.UserData
input *model.SearchInput
propTypes map[string]string
}

func SearchSchemaResolver(ctx context.Context) (map[string]interface{}, error) {
func SearchSchemaResolver(ctx context.Context, srchInput *model.SearchInput) (map[string]interface{}, error) {
userData, userDataErr := rbac.GetCache().GetUserData(ctx)
if userDataErr != nil {
return nil, userDataErr
}

// Check that shared cache has property types:
propTypes, err := rbac.GetCache().GetPropertyTypes(ctx, false)
if err != nil {
klog.Warningf("Error creating datatype map with err: [%s] ", err)
}

// Proceed if user's rbac data exists
searchSchemaResult := &SearchSchema{
pool: db.GetConnPool(ctx),
userData: userData,
pool: db.GetConnPool(ctx),
userData: userData,
input: srchInput,
propTypes: propTypes,
}
searchSchemaResult.buildSearchSchemaQuery(ctx)
return searchSchemaResult.searchSchemaResults(ctx)
Expand All @@ -53,14 +65,20 @@ func (s *SearchSchema) buildSearchSchemaQuery(ctx context.Context) {
ds := goqu.From(schemaTable)

//WHERE CLAUSE
var whereDs exp.ExpressionList
var whereDs []exp.Expression

// WHERE CLAUSE
if s.input != nil && len(s.input.Filters) > 0 {
whereDs, s.propTypes, _ = WhereClauseFilter(ctx, s.input, s.propTypes)
}

//get user info for logging
_, userInfo := rbac.GetCache().GetUserUID(ctx)

// if one of them is not nil, userData is not empty
if s.userData.CsResources != nil || s.userData.NsResources != nil || s.userData.ManagedClusters != nil {
whereDs = buildRbacWhereClause(ctx, s.userData, userInfo) // add rbac
whereDs = append(whereDs,
buildRbacWhereClause(ctx, s.userData, userInfo)) // add rbac
} else {
klog.Errorf("Error building search schema query: RBAC clause is required!"+
" None found for search schema query for user %s with uid %s ",
Expand All @@ -77,7 +95,7 @@ func (s *SearchSchema) buildSearchSchemaQuery(ctx context.Context) {
// Adding a LIMIT helps to speed up the query
// Adding a high number so as to get almost all the distinct properties from the database
if whereDs != nil {
selectDs = ds.SelectDistinct("prop").From(ds.Select(jsb).Where(whereDs).
selectDs = ds.SelectDistinct("prop").From(ds.Select(jsb).Where(whereDs...).
Limit(config.Cfg.QueryLimit * 100).As("schema"))
} else {
selectDs = ds.SelectDistinct("prop").From(ds.Select(jsb).
Expand Down
60 changes: 54 additions & 6 deletions pkg/resolver/searchSchema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,39 @@ import (
)

func Test_SearchSchema_Query(t *testing.T) {
searchInput := &model.SearchInput{}
// Create a SearchSchemaResolver instance with a mock connection pool.
resolver, _ := newMockSearchSchema(t)
resolver, _ := newMockSearchSchema(t, searchInput, rbac.UserData{CsResources: []rbac.Resource{}}, nil)

resolver.userData = rbac.UserData{CsResources: []rbac.Resource{}}
sql := `SELECT DISTINCT "prop" FROM (SELECT jsonb_object_keys(jsonb_strip_nulls("data")) AS "prop" FROM "search"."resources" WHERE ("cluster" = ANY ('{}')) LIMIT 100000) AS "schema"`
// Execute function
resolver.buildSearchSchemaQuery(context.TODO())

// Verify response
if resolver.query != sql {
t.Errorf("Expected sql query: %s but got %s", sql, resolver.query)
}
assert.Equalf(t, sql, resolver.query, "Expected sql query: %s but got %s", sql, resolver.query)
}

func Test_SearchSchema_Query_WithFilters(t *testing.T) {
value1 := "openshift"
searchInput := &model.SearchInput{Filters: []*model.SearchFilter{{Property: "namespace", Values: []*string{&value1}}}}
propTypesMock := map[string]string{"namespace": "string"}
// Create a SearchSchemaResolver instance with a mock connection pool.
resolver, _ := newMockSearchSchema(t, searchInput, rbac.UserData{CsResources: []rbac.Resource{}}, propTypesMock)

resolver.userData = rbac.UserData{CsResources: []rbac.Resource{}}
sql := `SELECT DISTINCT "prop" FROM (SELECT jsonb_object_keys(jsonb_strip_nulls("data")) AS "prop" FROM "search"."resources" WHERE ("data"->'namespace'?('openshift') AND ("cluster" = ANY ('{}'))) LIMIT 100000) AS "schema"`
// Execute function
resolver.buildSearchSchemaQuery(context.Background())

// Verify response
assert.Equal(t, sql, resolver.query)
}

func Test_SearchSchema_Results(t *testing.T) {
// Create a SearchSchemaResolver instance with a mock connection pool.
searchInput := &model.SearchInput{}
resolver, mockPool := newMockSearchSchema(t)
resolver, mockPool := newMockSearchSchema(t, searchInput, rbac.UserData{CsResources: []rbac.Resource{}}, nil)
csRes, nsRes, managedClusters := newUserData()
resolver.userData = rbac.UserData{CsResources: csRes, NsResources: nsRes, ManagedClusters: managedClusters}

Expand All @@ -55,9 +70,42 @@ func Test_SearchSchema_Results(t *testing.T) {
AssertStringArrayEqual(t, result, expectedResult, "Search schema results doesn't match.")
}

func Test_SearchSchema_Results_WithFilter(t *testing.T) {
// Create a SearchSchemaResolver instance with a mock connection pool.
value1 := "openshift"
searchInput := &model.SearchInput{Filters: []*model.SearchFilter{{Property: "namespace", Values: []*string{&value1}}}}
propTypesMock := map[string]string{"namespace": "string"}

resolver, mockPool := newMockSearchSchema(t, searchInput, rbac.UserData{CsResources: []rbac.Resource{}}, propTypesMock)
csRes, nsRes, managedClusters := newUserData()
resolver.userData = rbac.UserData{CsResources: csRes, NsResources: nsRes, ManagedClusters: managedClusters}

expectedList := []string{"cluster", "kind", "label", "name", "namespace", "status"}

expectedRes := map[string]interface{}{
"allProperties": expectedList,
}

// Mock the database queries.
//SELECT DISTINCT "prop" FROM (SELECT jsonb_object_keys(jsonb_strip_nulls("data")) AS "prop" FROM "search"."resources" LIMIT 100000) AS "schema"
mockRows := newMockRowsWithoutRBAC("../resolver/mocks/mock.json", searchInput, " ", 0)
// Mock the database query
mockPool.EXPECT().Query(gomock.Any(),
gomock.Eq(`SELECT DISTINCT "prop" FROM (SELECT jsonb_object_keys(jsonb_strip_nulls("data")) AS "prop" FROM "search"."resources" WHERE ("data"->'namespace'?('openshift') AND (("cluster" = ANY ('{"managed1","managed2"}')) OR ("data"?'_hubClusterResource' AND ((NOT("data"?'namespace') AND ((NOT("data"?'apigroup') AND data->'kind_plural'?'nodes') OR (data->'apigroup'?'storage.k8s.io' AND data->'kind_plural'?'csinodes'))) OR ((data->'namespace'?|'{"default"}' AND ((NOT("data"?'apigroup') AND data->'kind_plural'?'configmaps') OR (data->'apigroup'?'v4' AND data->'kind_plural'?'services'))) OR (data->'namespace'?|'{"ocm"}' AND ((data->'apigroup'?'v1' AND data->'kind_plural'?'pods') OR (data->'apigroup'?'v2' AND data->'kind_plural'?'deployments')))))))) LIMIT 100000) AS "schema"`),
).Return(mockRows, nil)
resolver.buildSearchSchemaQuery(context.Background())
res, _ := resolver.searchSchemaResults(context.Background())

result := stringArrayToPointer(res["allProperties"].([]string))
expectedResult := stringArrayToPointer(expectedRes["allProperties"].([]string))

AssertStringArrayEqual(t, result, expectedResult, "Search schema results doesn't match.")
}

func Test_SearchSchema_EmptyQueryNoUserData(t *testing.T) {
searchInput := &model.SearchInput{}
// Create a SearchSchemaResolver instance with a mock connection pool.
resolver, _ := newMockSearchSchema(t)
resolver, _ := newMockSearchSchema(t, searchInput, rbac.UserData{CsResources: []rbac.Resource{}}, nil)

resolver.userData = rbac.UserData{}
resolver.query = "mock Query"
Expand Down
7 changes: 5 additions & 2 deletions pkg/resolver/testHelper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,16 @@ func newMockSearchComplete(t *testing.T, input *model.SearchInput, property stri
}
return mockResolver, mockPool
}
func newMockSearchSchema(t *testing.T) (*SearchSchema, *pgxpoolmock.MockPgxPool) {
func newMockSearchSchema(t *testing.T, input *model.SearchInput, ud rbac.UserData, PropTypes map[string]string) (*SearchSchema, *pgxpoolmock.MockPgxPool) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockPool := pgxpoolmock.NewMockPgxPool(ctrl)

mockResolver := &SearchSchema{
pool: mockPool,
pool: mockPool,
input: input,
userData: ud,
propTypes: PropTypes,
}
return mockResolver, mockPool
}
Expand Down