diff --git a/pkg/coredata/control.go b/pkg/coredata/control.go index cb06ed350..202db1973 100644 --- a/pkg/coredata/control.go +++ b/pkg/coredata/control.go @@ -64,6 +64,7 @@ func (c *Controls) LoadByDocumentID( scope Scoper, documentID gid.GID, cursor *page.Cursor[ControlOrderField], + filter *ControlFilter, ) error { q := ` WITH ctrl AS ( @@ -96,11 +97,13 @@ FROM ctrl WHERE %s AND %s + AND %s ` - q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) + q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment()) args := pgx.NamedArgs{"document_id": documentID} maps.Copy(args, scope.SQLArguments()) + maps.Copy(args, filter.SQLArguments()) maps.Copy(args, cursor.SQLArguments()) rows, err := conn.Query(ctx, q, args) @@ -124,6 +127,7 @@ func (c *Controls) LoadByMeasureID( scope Scoper, measureID gid.GID, cursor *page.Cursor[ControlOrderField], + filter *ControlFilter, ) error { q := ` WITH ctrl AS ( @@ -156,11 +160,14 @@ FROM ctrl WHERE %s AND %s + AND %s ` - q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) + q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment()) args := pgx.NamedArgs{"measure_id": measureID} maps.Copy(args, scope.SQLArguments()) + maps.Copy(args, filter.SQLArguments()) + maps.Copy(args, cursor.SQLArguments()) rows, err := conn.Query(ctx, q, args) if err != nil { @@ -183,6 +190,7 @@ func (c *Controls) LoadByRiskID( scope Scoper, riskID gid.GID, cursor *page.Cursor[ControlOrderField], + filter *ControlFilter, ) error { q := ` WITH ctrl AS ( @@ -221,11 +229,13 @@ FROM ctrl WHERE %s AND %s + AND %s ` - q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) + q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment()) args := pgx.NamedArgs{"risk_id": riskID} maps.Copy(args, scope.SQLArguments()) + maps.Copy(args, filter.SQLArguments()) maps.Copy(args, cursor.SQLArguments()) rows, err := conn.Query(ctx, q, args) @@ -249,6 +259,7 @@ func (c *Controls) LoadByFrameworkID( scope Scoper, frameworkID gid.GID, cursor *page.Cursor[ControlOrderField], + filter *ControlFilter, ) error { q := ` SELECT @@ -266,11 +277,14 @@ WHERE %s AND framework_id = @framework_id AND %s + AND %s ` - q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) + q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment()) args := pgx.NamedArgs{"framework_id": frameworkID} maps.Copy(args, scope.SQLArguments()) + maps.Copy(args, filter.SQLArguments()) + maps.Copy(args, cursor.SQLArguments()) rows, err := conn.Query(ctx, q, args) if err != nil { @@ -293,6 +307,7 @@ func (c *Controls) LoadByOrganizationID( scope Scoper, organizationID gid.GID, cursor *page.Cursor[ControlOrderField], + filter *ControlFilter, ) error { q := ` WITH ctrl AS ( @@ -325,11 +340,14 @@ FROM ctrl WHERE %s AND %s + AND %s ` - q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) + q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment()) args := pgx.NamedArgs{"organization_id": organizationID} maps.Copy(args, scope.SQLArguments()) + maps.Copy(args, filter.SQLArguments()) + maps.Copy(args, cursor.SQLArguments()) rows, err := conn.Query(ctx, q, args) if err != nil { diff --git a/pkg/coredata/control_filter.go b/pkg/coredata/control_filter.go new file mode 100644 index 000000000..daaed8f39 --- /dev/null +++ b/pkg/coredata/control_filter.go @@ -0,0 +1,45 @@ +// Copyright (c) 2025 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package coredata + +import ( + "github.com/jackc/pgx/v5" +) + +type ( + ControlFilter struct { + query *string + } +) + +func NewControlFilter(query *string) *ControlFilter { + return &ControlFilter{ + query: query, + } +} + +func (f *ControlFilter) SQLArguments() pgx.StrictNamedArgs { + return pgx.StrictNamedArgs{ + "query": f.query, + } +} + +func (f *ControlFilter) SQLFragment() string { + if f.query == nil || *f.query == "" { + return "TRUE" + } + + return "search_vector @@ websearch_to_tsquery('simple', @query)" +} diff --git a/pkg/coredata/migrations/20250607T173748Z.sql b/pkg/coredata/migrations/20250607T173748Z.sql new file mode 100644 index 000000000..6a1919310 --- /dev/null +++ b/pkg/coredata/migrations/20250607T173748Z.sql @@ -0,0 +1,10 @@ +ALTER TABLE controls ADD COLUMN search_vector tsvector +GENERATED ALWAYS AS ( + to_tsvector('simple', + COALESCE(section_title, '') || ' ' || + COALESCE(name, '') || ' ' || + COALESCE(description, '') + ) +) STORED; + +CREATE INDEX controls_search_idx ON controls USING gin(search_vector); diff --git a/pkg/probo/control_service.go b/pkg/probo/control_service.go index ffcd71b52..17231dbf6 100644 --- a/pkg/probo/control_service.go +++ b/pkg/probo/control_service.go @@ -60,6 +60,7 @@ func (s ControlService) ListForDocumentID( ctx context.Context, documentID gid.GID, cursor *page.Cursor[coredata.ControlOrderField], + filter *coredata.ControlFilter, ) (*page.Page[*coredata.Control, coredata.ControlOrderField], error) { var controls coredata.Controls document := &coredata.Document{} @@ -71,7 +72,7 @@ func (s ControlService) ListForDocumentID( return fmt.Errorf("cannot load document: %w", err) } - return controls.LoadByDocumentID(ctx, conn, s.svc.scope, documentID, cursor) + return controls.LoadByDocumentID(ctx, conn, s.svc.scope, documentID, cursor, filter) }, ) @@ -86,6 +87,7 @@ func (s ControlService) ListForMeasureID( ctx context.Context, measureID gid.GID, cursor *page.Cursor[coredata.ControlOrderField], + filter *coredata.ControlFilter, ) (*page.Page[*coredata.Control, coredata.ControlOrderField], error) { var controls coredata.Controls measure := &coredata.Measure{} @@ -97,7 +99,7 @@ func (s ControlService) ListForMeasureID( return fmt.Errorf("cannot load measure: %w", err) } - return controls.LoadByMeasureID(ctx, conn, s.svc.scope, measureID, cursor) + return controls.LoadByMeasureID(ctx, conn, s.svc.scope, measureID, cursor, filter) }, ) @@ -356,6 +358,7 @@ func (s ControlService) ListForFrameworkID( ctx context.Context, frameworkID gid.GID, cursor *page.Cursor[coredata.ControlOrderField], + filter *coredata.ControlFilter, ) (*page.Page[*coredata.Control, coredata.ControlOrderField], error) { var controls coredata.Controls framework := &coredata.Framework{} @@ -373,6 +376,7 @@ func (s ControlService) ListForFrameworkID( s.svc.scope, framework.ID, cursor, + filter, ) }, ) @@ -388,6 +392,7 @@ func (s ControlService) ListForOrganizationID( ctx context.Context, organizationID gid.GID, cursor *page.Cursor[coredata.ControlOrderField], + filter *coredata.ControlFilter, ) (*page.Page[*coredata.Control, coredata.ControlOrderField], error) { var controls coredata.Controls organization := &coredata.Organization{} @@ -405,6 +410,7 @@ func (s ControlService) ListForOrganizationID( s.svc.scope, organization.ID, cursor, + filter, ) }, ) @@ -420,6 +426,7 @@ func (s ControlService) ListForRiskID( ctx context.Context, riskID gid.GID, cursor *page.Cursor[coredata.ControlOrderField], + filter *coredata.ControlFilter, ) (*page.Page[*coredata.Control, coredata.ControlOrderField], error) { var controls coredata.Controls risk := &coredata.Risk{} @@ -431,7 +438,7 @@ func (s ControlService) ListForRiskID( return fmt.Errorf("cannot load risk: %w", err) } - return controls.LoadByRiskID(ctx, conn, s.svc.scope, risk.ID, cursor) + return controls.LoadByRiskID(ctx, conn, s.svc.scope, risk.ID, cursor, filter) }, ) diff --git a/pkg/probo/framework_service.go b/pkg/probo/framework_service.go index de4e6d47f..8804a3340 100644 --- a/pkg/probo/framework_service.go +++ b/pkg/probo/framework_service.go @@ -302,7 +302,7 @@ func (s FrameworkService) ExportAudit( ) controls := coredata.Controls{} - if err := controls.LoadByFrameworkID(ctx, conn, s.svc.scope, frameworkID, cursor); err != nil { + if err := controls.LoadByFrameworkID(ctx, conn, s.svc.scope, frameworkID, cursor, coredata.NewControlFilter(nil)); err != nil { return fmt.Errorf("cannot load controls: %w", err) } diff --git a/pkg/server/api/console/v1/schema.graphql b/pkg/server/api/console/v1/schema.graphql index 97492071e..2b5832b00 100644 --- a/pkg/server/api/console/v1/schema.graphql +++ b/pkg/server/api/console/v1/schema.graphql @@ -370,7 +370,7 @@ enum DatumOrderField @goModel(model: "github.com/getprobo/probo/pkg/coredata.Dat DATA_SENSITIVITY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.DatumOrderFieldDataSensitivity") } -# Order Input Types +# Input Types input UserOrder @goModel( model: "github.com/getprobo/probo/pkg/server/api/console/v1/types.UserOrderBy" @@ -481,6 +481,11 @@ input DocumentVersionFilter { status: DocumentStatus } +# Input Types for Filtering +input ControlFilter { + query: String +} + # Core Types type Organization implements Node { id: ID! @@ -517,6 +522,7 @@ type Organization implements Node { last: Int before: CursorKey orderBy: ControlOrder + filter: ControlFilter ): ControlConnection! @goField(forceResolver: true) vendors( @@ -687,6 +693,7 @@ type Framework implements Node { last: Int before: CursorKey orderBy: ControlOrder + filter: ControlFilter ): ControlConnection! @goField(forceResolver: true) createdAt: Datetime! @@ -758,6 +765,7 @@ type Measure implements Node { last: Int before: CursorKey orderBy: ControlOrder + filter: ControlFilter ): ControlConnection! @goField(forceResolver: true) createdAt: Datetime! @@ -830,6 +838,7 @@ type Document implements Node { last: Int before: CursorKey orderBy: ControlOrder + filter: ControlFilter ): ControlConnection! @goField(forceResolver: true) createdAt: Datetime! @@ -875,6 +884,7 @@ type Risk implements Node { last: Int before: CursorKey orderBy: ControlOrder + filter: ControlFilter ): ControlConnection! @goField(forceResolver: true) createdAt: Datetime! diff --git a/pkg/server/api/console/v1/schema/schema.go b/pkg/server/api/console/v1/schema/schema.go index 8186ecc96..0fd9bebe1 100644 --- a/pkg/server/api/console/v1/schema/schema.go +++ b/pkg/server/api/console/v1/schema/schema.go @@ -322,7 +322,7 @@ type ComplexityRoot struct { } Document struct { - Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) int + Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) int CreatedAt func(childComplexity int) int CurrentPublishedVersion func(childComplexity int) int Description func(childComplexity int) int @@ -424,7 +424,7 @@ type ComplexityRoot struct { } Framework struct { - Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) int + Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) int CreatedAt func(childComplexity int) int Description func(childComplexity int) int ID func(childComplexity int) int @@ -465,7 +465,7 @@ type ComplexityRoot struct { Measure struct { Category func(childComplexity int) int - Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) int + Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) int CreatedAt func(childComplexity int) int Description func(childComplexity int) int Evidences func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.EvidenceOrderBy) int @@ -561,7 +561,7 @@ type ComplexityRoot struct { Organization struct { Assets func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AssetOrder) int Connectors func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ConnectorOrder) int - Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) int + Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) int CreatedAt func(childComplexity int) int Data func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DatumOrder) int Documents func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy) int @@ -650,7 +650,7 @@ type ComplexityRoot struct { Risk struct { Category func(childComplexity int) int - Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) int + Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) int CreatedAt func(childComplexity int) int Description func(childComplexity int) int Documents func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy) int @@ -908,7 +908,7 @@ type DocumentResolver interface { Owner(ctx context.Context, obj *types.Document) (*types.People, error) Organization(ctx context.Context, obj *types.Document) (*types.Organization, error) Versions(ctx context.Context, obj *types.Document, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentVersionOrderBy, filter *types.DocumentVersionFilter) (*types.DocumentVersionConnection, error) - Controls(ctx context.Context, obj *types.Document, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) (*types.ControlConnection, error) + Controls(ctx context.Context, obj *types.Document, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) } type DocumentVersionResolver interface { Document(ctx context.Context, obj *types.DocumentVersion) (*types.Document, error) @@ -932,13 +932,13 @@ type EvidenceResolver interface { } type FrameworkResolver interface { Organization(ctx context.Context, obj *types.Framework) (*types.Organization, error) - Controls(ctx context.Context, obj *types.Framework, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) (*types.ControlConnection, error) + Controls(ctx context.Context, obj *types.Framework, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) } type MeasureResolver interface { Evidences(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.EvidenceOrderBy) (*types.EvidenceConnection, error) Tasks(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TaskOrderBy) (*types.TaskConnection, error) Risks(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.RiskOrderBy) (*types.RiskConnection, error) - Controls(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) (*types.ControlConnection, error) + Controls(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) } type MutationResolver interface { CreateOrganization(ctx context.Context, input types.CreateOrganizationInput) (*types.CreateOrganizationPayload, error) @@ -1015,7 +1015,7 @@ type OrganizationResolver interface { Users(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.UserOrderBy) (*types.UserConnection, error) Connectors(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ConnectorOrder) (*types.ConnectorConnection, error) Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.FrameworkOrderBy) (*types.FrameworkConnection, error) - Controls(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) (*types.ControlConnection, error) + Controls(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) Vendors(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorOrderBy) (*types.VendorConnection, error) Peoples(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.PeopleOrderBy) (*types.PeopleConnection, error) Documents(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy) (*types.DocumentConnection, error) @@ -1034,7 +1034,7 @@ type RiskResolver interface { Organization(ctx context.Context, obj *types.Risk) (*types.Organization, error) Measures(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy) (*types.MeasureConnection, error) Documents(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy) (*types.DocumentConnection, error) - Controls(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) (*types.ControlConnection, error) + Controls(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) } type TaskResolver interface { AssignedTo(ctx context.Context, obj *types.Task) (*types.People, error) @@ -1801,7 +1801,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return 0, false } - return e.complexity.Document.Controls(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.ControlOrderBy)), true + return e.complexity.Document.Controls(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.ControlOrderBy), args["filter"].(*types.ControlFilter)), true case "Document.createdAt": if e.complexity.Document.CreatedAt == nil { @@ -2257,7 +2257,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return 0, false } - return e.complexity.Framework.Controls(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.ControlOrderBy)), true + return e.complexity.Framework.Controls(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.ControlOrderBy), args["filter"].(*types.ControlFilter)), true case "Framework.createdAt": if e.complexity.Framework.CreatedAt == nil { @@ -2381,7 +2381,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return 0, false } - return e.complexity.Measure.Controls(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.ControlOrderBy)), true + return e.complexity.Measure.Controls(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.ControlOrderBy), args["filter"].(*types.ControlFilter)), true case "Measure.createdAt": if e.complexity.Measure.CreatedAt == nil { @@ -3339,7 +3339,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return 0, false } - return e.complexity.Organization.Controls(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.ControlOrderBy)), true + return e.complexity.Organization.Controls(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.ControlOrderBy), args["filter"].(*types.ControlFilter)), true case "Organization.createdAt": if e.complexity.Organization.CreatedAt == nil { @@ -3723,7 +3723,7 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return 0, false } - return e.complexity.Risk.Controls(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.ControlOrderBy)), true + return e.complexity.Risk.Controls(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.ControlOrderBy), args["filter"].(*types.ControlFilter)), true case "Risk.createdAt": if e.complexity.Risk.CreatedAt == nil { @@ -4643,6 +4643,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputAssignTaskInput, ec.unmarshalInputConfirmEmailInput, ec.unmarshalInputConnectorOrder, + ec.unmarshalInputControlFilter, ec.unmarshalInputControlOrder, ec.unmarshalInputCreateAssetInput, ec.unmarshalInputCreateControlDocumentMappingInput, @@ -5193,7 +5194,7 @@ enum DatumOrderField @goModel(model: "github.com/getprobo/probo/pkg/coredata.Dat DATA_SENSITIVITY @goEnum(value: "github.com/getprobo/probo/pkg/coredata.DatumOrderFieldDataSensitivity") } -# Order Input Types +# Input Types input UserOrder @goModel( model: "github.com/getprobo/probo/pkg/server/api/console/v1/types.UserOrderBy" @@ -5304,6 +5305,11 @@ input DocumentVersionFilter { status: DocumentStatus } +# Input Types for Filtering +input ControlFilter { + query: String +} + # Core Types type Organization implements Node { id: ID! @@ -5340,6 +5346,7 @@ type Organization implements Node { last: Int before: CursorKey orderBy: ControlOrder + filter: ControlFilter ): ControlConnection! @goField(forceResolver: true) vendors( @@ -5510,6 +5517,7 @@ type Framework implements Node { last: Int before: CursorKey orderBy: ControlOrder + filter: ControlFilter ): ControlConnection! @goField(forceResolver: true) createdAt: Datetime! @@ -5581,6 +5589,7 @@ type Measure implements Node { last: Int before: CursorKey orderBy: ControlOrder + filter: ControlFilter ): ControlConnection! @goField(forceResolver: true) createdAt: Datetime! @@ -5653,6 +5662,7 @@ type Document implements Node { last: Int before: CursorKey orderBy: ControlOrder + filter: ControlFilter ): ControlConnection! @goField(forceResolver: true) createdAt: Datetime! @@ -5698,6 +5708,7 @@ type Risk implements Node { last: Int before: CursorKey orderBy: ControlOrder + filter: ControlFilter ): ControlConnection! @goField(forceResolver: true) createdAt: Datetime! @@ -7454,6 +7465,11 @@ func (ec *executionContext) field_Document_controls_args(ctx context.Context, ra return nil, err } args["orderBy"] = arg4 + arg5, err := ec.field_Document_controls_argsFilter(ctx, rawArgs) + if err != nil { + return nil, err + } + args["filter"] = arg5 return args, nil } func (ec *executionContext) field_Document_controls_argsFirst( @@ -7521,6 +7537,19 @@ func (ec *executionContext) field_Document_controls_argsOrderBy( return zeroVal, nil } +func (ec *executionContext) field_Document_controls_argsFilter( + ctx context.Context, + rawArgs map[string]any, +) (*types.ControlFilter, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("filter")) + if tmp, ok := rawArgs["filter"]; ok { + return ec.unmarshalOControlFilter2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐControlFilter(ctx, tmp) + } + + var zeroVal *types.ControlFilter + return zeroVal, nil +} + func (ec *executionContext) field_Document_versions_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -7662,6 +7691,11 @@ func (ec *executionContext) field_Framework_controls_args(ctx context.Context, r return nil, err } args["orderBy"] = arg4 + arg5, err := ec.field_Framework_controls_argsFilter(ctx, rawArgs) + if err != nil { + return nil, err + } + args["filter"] = arg5 return args, nil } func (ec *executionContext) field_Framework_controls_argsFirst( @@ -7729,6 +7763,19 @@ func (ec *executionContext) field_Framework_controls_argsOrderBy( return zeroVal, nil } +func (ec *executionContext) field_Framework_controls_argsFilter( + ctx context.Context, + rawArgs map[string]any, +) (*types.ControlFilter, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("filter")) + if tmp, ok := rawArgs["filter"]; ok { + return ec.unmarshalOControlFilter2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐControlFilter(ctx, tmp) + } + + var zeroVal *types.ControlFilter + return zeroVal, nil +} + func (ec *executionContext) field_Measure_controls_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -7757,6 +7804,11 @@ func (ec *executionContext) field_Measure_controls_args(ctx context.Context, raw return nil, err } args["orderBy"] = arg4 + arg5, err := ec.field_Measure_controls_argsFilter(ctx, rawArgs) + if err != nil { + return nil, err + } + args["filter"] = arg5 return args, nil } func (ec *executionContext) field_Measure_controls_argsFirst( @@ -7824,6 +7876,19 @@ func (ec *executionContext) field_Measure_controls_argsOrderBy( return zeroVal, nil } +func (ec *executionContext) field_Measure_controls_argsFilter( + ctx context.Context, + rawArgs map[string]any, +) (*types.ControlFilter, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("filter")) + if tmp, ok := rawArgs["filter"]; ok { + return ec.unmarshalOControlFilter2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐControlFilter(ctx, tmp) + } + + var zeroVal *types.ControlFilter + return zeroVal, nil +} + func (ec *executionContext) field_Measure_evidences_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -9891,6 +9956,11 @@ func (ec *executionContext) field_Organization_controls_args(ctx context.Context return nil, err } args["orderBy"] = arg4 + arg5, err := ec.field_Organization_controls_argsFilter(ctx, rawArgs) + if err != nil { + return nil, err + } + args["filter"] = arg5 return args, nil } func (ec *executionContext) field_Organization_controls_argsFirst( @@ -9958,6 +10028,19 @@ func (ec *executionContext) field_Organization_controls_argsOrderBy( return zeroVal, nil } +func (ec *executionContext) field_Organization_controls_argsFilter( + ctx context.Context, + rawArgs map[string]any, +) (*types.ControlFilter, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("filter")) + if tmp, ok := rawArgs["filter"]; ok { + return ec.unmarshalOControlFilter2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐControlFilter(ctx, tmp) + } + + var zeroVal *types.ControlFilter + return zeroVal, nil +} + func (ec *executionContext) field_Organization_data_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -10887,6 +10970,11 @@ func (ec *executionContext) field_Risk_controls_args(ctx context.Context, rawArg return nil, err } args["orderBy"] = arg4 + arg5, err := ec.field_Risk_controls_argsFilter(ctx, rawArgs) + if err != nil { + return nil, err + } + args["filter"] = arg5 return args, nil } func (ec *executionContext) field_Risk_controls_argsFirst( @@ -10954,6 +11042,19 @@ func (ec *executionContext) field_Risk_controls_argsOrderBy( return zeroVal, nil } +func (ec *executionContext) field_Risk_controls_argsFilter( + ctx context.Context, + rawArgs map[string]any, +) (*types.ControlFilter, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("filter")) + if tmp, ok := rawArgs["filter"]; ok { + return ec.unmarshalOControlFilter2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐControlFilter(ctx, tmp) + } + + var zeroVal *types.ControlFilter + return zeroVal, nil +} + func (ec *executionContext) field_Risk_documents_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -16991,7 +17092,7 @@ func (ec *executionContext) _Document_controls(ctx context.Context, field graphq }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Document().Controls(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.ControlOrderBy)) + return ec.resolvers.Document().Controls(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.ControlOrderBy), fc.Args["filter"].(*types.ControlFilter)) }) if err != nil { ec.Error(ctx, err) @@ -19993,7 +20094,7 @@ func (ec *executionContext) _Framework_controls(ctx context.Context, field graph }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Framework().Controls(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.ControlOrderBy)) + return ec.resolvers.Framework().Controls(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.ControlOrderBy), fc.Args["filter"].(*types.ControlFilter)) }) if err != nil { ec.Error(ctx, err) @@ -20991,7 +21092,7 @@ func (ec *executionContext) _Measure_controls(ctx context.Context, field graphql }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Measure().Controls(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.ControlOrderBy)) + return ec.resolvers.Measure().Controls(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.ControlOrderBy), fc.Args["filter"].(*types.ControlFilter)) }) if err != nil { ec.Error(ctx, err) @@ -25700,7 +25801,7 @@ func (ec *executionContext) _Organization_controls(ctx context.Context, field gr }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Organization().Controls(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.ControlOrderBy)) + return ec.resolvers.Organization().Controls(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.ControlOrderBy), fc.Args["filter"].(*types.ControlFilter)) }) if err != nil { ec.Error(ctx, err) @@ -28825,7 +28926,7 @@ func (ec *executionContext) _Risk_controls(ctx context.Context, field graphql.Co }() resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Risk().Controls(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.ControlOrderBy)) + return ec.resolvers.Risk().Controls(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.ControlOrderBy), fc.Args["filter"].(*types.ControlFilter)) }) if err != nil { ec.Error(ctx, err) @@ -36823,6 +36924,33 @@ func (ec *executionContext) unmarshalInputConnectorOrder(ctx context.Context, ob return it, nil } +func (ec *executionContext) unmarshalInputControlFilter(ctx context.Context, obj any) (types.ControlFilter, error) { + var it types.ControlFilter + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"query"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "query": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("query")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.Query = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputControlOrder(ctx context.Context, obj any) (types.ControlOrderBy, error) { var it types.ControlOrderBy asMap := map[string]any{} @@ -53558,6 +53686,14 @@ func (ec *executionContext) unmarshalOConnectorOrder2ᚖgithubᚗcomᚋgetprobo return &res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalOControlFilter2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐControlFilter(ctx context.Context, v any) (*types.ControlFilter, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputControlFilter(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalOControlOrder2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐControlOrderBy(ctx context.Context, v any) (*types.ControlOrderBy, error) { if v == nil { return nil, nil diff --git a/pkg/server/api/console/v1/types/types.go b/pkg/server/api/console/v1/types/types.go index 148523f95..9961c404f 100644 --- a/pkg/server/api/console/v1/types/types.go +++ b/pkg/server/api/console/v1/types/types.go @@ -143,6 +143,10 @@ type ControlEdge struct { Node *Control `json:"node"` } +type ControlFilter struct { + Query *string `json:"query,omitempty"` +} + type CreateAssetInput struct { OrganizationID gid.GID `json:"organizationId"` Name string `json:"name"` diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go index bbe151361..90a78d2ee 100644 --- a/pkg/server/api/console/v1/v1_resolver.go +++ b/pkg/server/api/console/v1/v1_resolver.go @@ -261,7 +261,7 @@ func (r *documentResolver) Versions(ctx context.Context, obj *types.Document, fi } // Controls is the resolver for the controls field. -func (r *documentResolver) Controls(ctx context.Context, obj *types.Document, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) (*types.ControlConnection, error) { +func (r *documentResolver) Controls(ctx context.Context, obj *types.Document, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID()) pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ @@ -277,7 +277,12 @@ func (r *documentResolver) Controls(ctx context.Context, obj *types.Document, fi cursor := types.NewCursor(first, after, last, before, pageOrderBy) - page, err := svc.Controls.ListForDocumentID(ctx, obj.ID, cursor) + var controlFilter = coredata.NewControlFilter(nil) + if filter != nil { + controlFilter = coredata.NewControlFilter(filter.Query) + } + + page, err := svc.Controls.ListForDocumentID(ctx, obj.ID, cursor, controlFilter) if err != nil { panic(fmt.Errorf("cannot list document controls: %w", err)) } @@ -489,7 +494,7 @@ func (r *frameworkResolver) Organization(ctx context.Context, obj *types.Framewo } // Controls is the resolver for the controls field. -func (r *frameworkResolver) Controls(ctx context.Context, obj *types.Framework, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) (*types.ControlConnection, error) { +func (r *frameworkResolver) Controls(ctx context.Context, obj *types.Framework, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID()) pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ @@ -505,7 +510,12 @@ func (r *frameworkResolver) Controls(ctx context.Context, obj *types.Framework, cursor := types.NewCursor(first, after, last, before, pageOrderBy) - page, err := svc.Controls.ListForFrameworkID(ctx, obj.ID, cursor) + var controlFilter = coredata.NewControlFilter(nil) + if filter != nil { + controlFilter = coredata.NewControlFilter(filter.Query) + } + + page, err := svc.Controls.ListForFrameworkID(ctx, obj.ID, cursor, controlFilter) if err != nil { return nil, fmt.Errorf("cannot list controls: %w", err) } @@ -589,7 +599,7 @@ func (r *measureResolver) Risks(ctx context.Context, obj *types.Measure, first * } // Controls is the resolver for the controls field. -func (r *measureResolver) Controls(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) (*types.ControlConnection, error) { +func (r *measureResolver) Controls(ctx context.Context, obj *types.Measure, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID()) pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ @@ -605,7 +615,12 @@ func (r *measureResolver) Controls(ctx context.Context, obj *types.Measure, firs cursor := types.NewCursor(first, after, last, before, pageOrderBy) - page, err := svc.Controls.ListForMeasureID(ctx, obj.ID, cursor) + var controlFilter = coredata.NewControlFilter(nil) + if filter != nil { + controlFilter = coredata.NewControlFilter(filter.Query) + } + + page, err := svc.Controls.ListForMeasureID(ctx, obj.ID, cursor, controlFilter) if err != nil { return nil, fmt.Errorf("cannot list measure controls: %w", err) } @@ -1974,7 +1989,7 @@ func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organi } // Controls is the resolver for the controls field. -func (r *organizationResolver) Controls(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) (*types.ControlConnection, error) { +func (r *organizationResolver) Controls(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID()) pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ @@ -1990,7 +2005,12 @@ func (r *organizationResolver) Controls(ctx context.Context, obj *types.Organiza cursor := types.NewCursor(first, after, last, before, pageOrderBy) - page, err := svc.Controls.ListForOrganizationID(ctx, obj.ID, cursor) + var controlFilter = coredata.NewControlFilter(nil) + if filter != nil { + controlFilter = coredata.NewControlFilter(filter.Query) + } + + page, err := svc.Controls.ListForOrganizationID(ctx, obj.ID, cursor, controlFilter) if err != nil { return nil, fmt.Errorf("cannot list controls: %w", err) } @@ -1998,11 +2018,6 @@ func (r *organizationResolver) Controls(ctx context.Context, obj *types.Organiza return types.NewControlConnection(page), nil } -// // Controls is the resolver for the controls field. -// func (r *organizationResolver) Controls(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) (*types.ControlConnection, error) { -// panic(fmt.Errorf("not implemented: Controls - controls")) -// } - // Vendors is the resolver for the vendors field. func (r *organizationResolver) Vendors(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorOrderBy) (*types.VendorConnection, error) { svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID()) @@ -2412,7 +2427,7 @@ func (r *riskResolver) Documents(ctx context.Context, obj *types.Risk, first *in } // Controls is the resolver for the controls field. -func (r *riskResolver) Controls(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) (*types.ControlConnection, error) { +func (r *riskResolver) Controls(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID()) pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ @@ -2428,7 +2443,12 @@ func (r *riskResolver) Controls(ctx context.Context, obj *types.Risk, first *int cursor := types.NewCursor(first, after, last, before, pageOrderBy) - page, err := svc.Controls.ListForRiskID(ctx, obj.ID, cursor) + var controlFilter *coredata.ControlFilter + if filter != nil { + controlFilter = coredata.NewControlFilter(filter.Query) + } + + page, err := svc.Controls.ListForRiskID(ctx, obj.ID, cursor, controlFilter) if err != nil { panic(fmt.Errorf("cannot list risk controls: %w", err)) }