From 14380aa635e2ddf5b6d4fcea98ca9c0c4e99a4ba Mon Sep 17 00:00:00 2001 From: gearnode Date: Thu, 30 Jan 2025 21:37:48 -0800 Subject: [PATCH] Add evidence state history Signed-off-by: gearnode --- pkg/api/console/v1/schema.graphql | 27 + pkg/api/console/v1/schema/schema.go | 950 +++++++++++++++++- .../v1/types/evidence_state_transition.go | 59 ++ pkg/api/console/v1/types/types.go | 30 +- pkg/api/console/v1/v1_resolver.go | 16 + pkg/probo/probo.go | 27 + 6 files changed, 1094 insertions(+), 15 deletions(-) create mode 100644 pkg/api/console/v1/types/evidence_state_transition.go diff --git a/pkg/api/console/v1/schema.graphql b/pkg/api/console/v1/schema.graphql index a1daf0aa7..8cbc5e275 100644 --- a/pkg/api/console/v1/schema.graphql +++ b/pkg/api/console/v1/schema.graphql @@ -249,6 +249,14 @@ type Evidence implements Node { id: ID! fileUrl: String! state: EvidenceState! + + stateTransisions( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): EvidenceStateTransitionConnection! @goField(forceResolver: true) + createdAt: Datetime! updatedAt: Datetime! } @@ -256,3 +264,22 @@ type Evidence implements Node { type Query { node(id: ID!): Node! } + +type EvidenceStateTransitionConnection { + edges: [EvidenceStateTransitionEdge!]! + pageInfo: PageInfo! +} + +type EvidenceStateTransitionEdge { + cursor: CursorKey! + node: EvidenceStateTransition! +} + +type EvidenceStateTransition { + id: ID! + fromState: EvidenceState + toState: EvidenceState! + reason: String + createdAt: Datetime! + updatedAt: Datetime! +} diff --git a/pkg/api/console/v1/schema/schema.go b/pkg/api/console/v1/schema/schema.go index d88f0eea6..67584486f 100644 --- a/pkg/api/console/v1/schema/schema.go +++ b/pkg/api/console/v1/schema/schema.go @@ -42,6 +42,7 @@ type Config struct { type ResolverRoot interface { Control() ControlResolver + Evidence() EvidenceResolver Framework() FrameworkResolver Organization() OrganizationResolver Query() QueryResolver @@ -93,11 +94,12 @@ type ComplexityRoot struct { } Evidence struct { - CreatedAt func(childComplexity int) int - FileURL func(childComplexity int) int - ID func(childComplexity int) int - State func(childComplexity int) int - UpdatedAt func(childComplexity int) int + CreatedAt func(childComplexity int) int + FileURL func(childComplexity int) int + ID func(childComplexity int) int + State func(childComplexity int) int + StateTransisions func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int + UpdatedAt func(childComplexity int) int } EvidenceConnection struct { @@ -110,6 +112,25 @@ type ComplexityRoot struct { Node func(childComplexity int) int } + EvidenceStateTransition struct { + CreatedAt func(childComplexity int) int + FromState func(childComplexity int) int + ID func(childComplexity int) int + Reason func(childComplexity int) int + ToState func(childComplexity int) int + UpdatedAt func(childComplexity int) int + } + + EvidenceStateTransitionConnection struct { + Edges func(childComplexity int) int + PageInfo func(childComplexity int) int + } + + EvidenceStateTransitionEdge struct { + Cursor func(childComplexity int) int + Node func(childComplexity int) int + } + Framework struct { Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int CreatedAt func(childComplexity int) int @@ -231,6 +252,9 @@ type ControlResolver interface { StateTransisions(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.ControlStateTransitionConnection, error) Tasks(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.TaskConnection, error) } +type EvidenceResolver interface { + StateTransisions(ctx context.Context, obj *types.Evidence, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.EvidenceStateTransitionConnection, error) +} type FrameworkResolver interface { Controls(ctx context.Context, obj *types.Framework, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.ControlConnection, error) } @@ -458,6 +482,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Evidence.State(childComplexity), true + case "Evidence.stateTransisions": + if e.complexity.Evidence.StateTransisions == nil { + break + } + + args, err := ec.field_Evidence_stateTransisions_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Evidence.StateTransisions(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey)), true + case "Evidence.updatedAt": if e.complexity.Evidence.UpdatedAt == nil { break @@ -493,6 +529,76 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.EvidenceEdge.Node(childComplexity), true + case "EvidenceStateTransition.createdAt": + if e.complexity.EvidenceStateTransition.CreatedAt == nil { + break + } + + return e.complexity.EvidenceStateTransition.CreatedAt(childComplexity), true + + case "EvidenceStateTransition.fromState": + if e.complexity.EvidenceStateTransition.FromState == nil { + break + } + + return e.complexity.EvidenceStateTransition.FromState(childComplexity), true + + case "EvidenceStateTransition.id": + if e.complexity.EvidenceStateTransition.ID == nil { + break + } + + return e.complexity.EvidenceStateTransition.ID(childComplexity), true + + case "EvidenceStateTransition.reason": + if e.complexity.EvidenceStateTransition.Reason == nil { + break + } + + return e.complexity.EvidenceStateTransition.Reason(childComplexity), true + + case "EvidenceStateTransition.toState": + if e.complexity.EvidenceStateTransition.ToState == nil { + break + } + + return e.complexity.EvidenceStateTransition.ToState(childComplexity), true + + case "EvidenceStateTransition.updatedAt": + if e.complexity.EvidenceStateTransition.UpdatedAt == nil { + break + } + + return e.complexity.EvidenceStateTransition.UpdatedAt(childComplexity), true + + case "EvidenceStateTransitionConnection.edges": + if e.complexity.EvidenceStateTransitionConnection.Edges == nil { + break + } + + return e.complexity.EvidenceStateTransitionConnection.Edges(childComplexity), true + + case "EvidenceStateTransitionConnection.pageInfo": + if e.complexity.EvidenceStateTransitionConnection.PageInfo == nil { + break + } + + return e.complexity.EvidenceStateTransitionConnection.PageInfo(childComplexity), true + + case "EvidenceStateTransitionEdge.cursor": + if e.complexity.EvidenceStateTransitionEdge.Cursor == nil { + break + } + + return e.complexity.EvidenceStateTransitionEdge.Cursor(childComplexity), true + + case "EvidenceStateTransitionEdge.node": + if e.complexity.EvidenceStateTransitionEdge.Node == nil { + break + } + + return e.complexity.EvidenceStateTransitionEdge.Node(childComplexity), true + case "Framework.controls": if e.complexity.Framework.Controls == nil { break @@ -1302,6 +1408,14 @@ type Evidence implements Node { id: ID! fileUrl: String! state: EvidenceState! + + stateTransisions( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): EvidenceStateTransitionConnection! @goField(forceResolver: true) + createdAt: Datetime! updatedAt: Datetime! } @@ -1309,6 +1423,25 @@ type Evidence implements Node { type Query { node(id: ID!): Node! } + +type EvidenceStateTransitionConnection { + edges: [EvidenceStateTransitionEdge!]! + pageInfo: PageInfo! +} + +type EvidenceStateTransitionEdge { + cursor: CursorKey! + node: EvidenceStateTransition! +} + +type EvidenceStateTransition { + id: ID! + fromState: EvidenceState + toState: EvidenceState! + reason: String + createdAt: Datetime! + updatedAt: Datetime! +} `, BuiltIn: false}, } var parsedSchema = gqlparser.MustLoadSchema(sources...) @@ -1471,6 +1604,83 @@ func (ec *executionContext) field_Control_tasks_argsBefore( return zeroVal, nil } +func (ec *executionContext) field_Evidence_stateTransisions_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Evidence_stateTransisions_argsFirst(ctx, rawArgs) + if err != nil { + return nil, err + } + args["first"] = arg0 + arg1, err := ec.field_Evidence_stateTransisions_argsAfter(ctx, rawArgs) + if err != nil { + return nil, err + } + args["after"] = arg1 + arg2, err := ec.field_Evidence_stateTransisions_argsLast(ctx, rawArgs) + if err != nil { + return nil, err + } + args["last"] = arg2 + arg3, err := ec.field_Evidence_stateTransisions_argsBefore(ctx, rawArgs) + if err != nil { + return nil, err + } + args["before"] = arg3 + return args, nil +} +func (ec *executionContext) field_Evidence_stateTransisions_argsFirst( + ctx context.Context, + rawArgs map[string]any, +) (*int, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("first")) + if tmp, ok := rawArgs["first"]; ok { + return ec.unmarshalOInt2ᚖint(ctx, tmp) + } + + var zeroVal *int + return zeroVal, nil +} + +func (ec *executionContext) field_Evidence_stateTransisions_argsAfter( + ctx context.Context, + rawArgs map[string]any, +) (*page.CursorKey, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after")) + if tmp, ok := rawArgs["after"]; ok { + return ec.unmarshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx, tmp) + } + + var zeroVal *page.CursorKey + return zeroVal, nil +} + +func (ec *executionContext) field_Evidence_stateTransisions_argsLast( + ctx context.Context, + rawArgs map[string]any, +) (*int, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("last")) + if tmp, ok := rawArgs["last"]; ok { + return ec.unmarshalOInt2ᚖint(ctx, tmp) + } + + var zeroVal *int + return zeroVal, nil +} + +func (ec *executionContext) field_Evidence_stateTransisions_argsBefore( + ctx context.Context, + rawArgs map[string]any, +) (*page.CursorKey, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before")) + if tmp, ok := rawArgs["before"]; ok { + return ec.unmarshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx, tmp) + } + + var zeroVal *page.CursorKey + return zeroVal, nil +} + func (ec *executionContext) field_Framework_controls_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -3063,6 +3273,55 @@ func (ec *executionContext) fieldContext_Evidence_state(_ context.Context, field return fc, nil } +func (ec *executionContext) _Evidence_stateTransisions(ctx context.Context, field graphql.CollectedField, obj *types.Evidence) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Evidence_stateTransisions(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Evidence().StateTransisions(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*types.EvidenceStateTransitionConnection) + fc.Result = res + return ec.marshalNEvidenceStateTransitionConnection2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceStateTransitionConnection(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Evidence_stateTransisions(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Evidence", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "edges": + return ec.fieldContext_EvidenceStateTransitionConnection_edges(ctx, field) + case "pageInfo": + return ec.fieldContext_EvidenceStateTransitionConnection_pageInfo(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type EvidenceStateTransitionConnection", field.Name) + }, + } + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Evidence_stateTransisions_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Evidence_createdAt(ctx context.Context, field graphql.CollectedField, obj *types.Evidence) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Evidence_createdAt(ctx, field) if err != nil { @@ -3308,6 +3567,8 @@ func (ec *executionContext) fieldContext_EvidenceEdge_node(_ context.Context, fi return ec.fieldContext_Evidence_fileUrl(ctx, field) case "state": return ec.fieldContext_Evidence_state(ctx, field) + case "stateTransisions": + return ec.fieldContext_Evidence_stateTransisions(ctx, field) case "createdAt": return ec.fieldContext_Evidence_createdAt(ctx, field) case "updatedAt": @@ -3319,6 +3580,410 @@ func (ec *executionContext) fieldContext_EvidenceEdge_node(_ context.Context, fi return fc, nil } +func (ec *executionContext) _EvidenceStateTransition_id(ctx context.Context, field graphql.CollectedField, obj *types.EvidenceStateTransition) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_EvidenceStateTransition_id(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.ID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(gid.GID) + fc.Result = res + return ec.marshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋgidᚐGID(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_EvidenceStateTransition_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "EvidenceStateTransition", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _EvidenceStateTransition_fromState(ctx context.Context, field graphql.CollectedField, obj *types.EvidenceStateTransition) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_EvidenceStateTransition_fromState(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.FromState, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*types.EvidenceState) + fc.Result = res + return ec.marshalOEvidenceState2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceState(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_EvidenceStateTransition_fromState(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "EvidenceStateTransition", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type EvidenceState does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _EvidenceStateTransition_toState(ctx context.Context, field graphql.CollectedField, obj *types.EvidenceStateTransition) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_EvidenceStateTransition_toState(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.ToState, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(types.EvidenceState) + fc.Result = res + return ec.marshalNEvidenceState2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceState(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_EvidenceStateTransition_toState(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "EvidenceStateTransition", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type EvidenceState does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _EvidenceStateTransition_reason(ctx context.Context, field graphql.CollectedField, obj *types.EvidenceStateTransition) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_EvidenceStateTransition_reason(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Reason, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_EvidenceStateTransition_reason(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "EvidenceStateTransition", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _EvidenceStateTransition_createdAt(ctx context.Context, field graphql.CollectedField, obj *types.EvidenceStateTransition) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_EvidenceStateTransition_createdAt(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.CreatedAt, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(time.Time) + fc.Result = res + return ec.marshalNDatetime2timeᚐTime(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_EvidenceStateTransition_createdAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "EvidenceStateTransition", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Datetime does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _EvidenceStateTransition_updatedAt(ctx context.Context, field graphql.CollectedField, obj *types.EvidenceStateTransition) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_EvidenceStateTransition_updatedAt(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.UpdatedAt, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(time.Time) + fc.Result = res + return ec.marshalNDatetime2timeᚐTime(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_EvidenceStateTransition_updatedAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "EvidenceStateTransition", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Datetime does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _EvidenceStateTransitionConnection_edges(ctx context.Context, field graphql.CollectedField, obj *types.EvidenceStateTransitionConnection) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_EvidenceStateTransitionConnection_edges(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Edges, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*types.EvidenceStateTransitionEdge) + fc.Result = res + return ec.marshalNEvidenceStateTransitionEdge2ᚕᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceStateTransitionEdgeᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_EvidenceStateTransitionConnection_edges(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "EvidenceStateTransitionConnection", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "cursor": + return ec.fieldContext_EvidenceStateTransitionEdge_cursor(ctx, field) + case "node": + return ec.fieldContext_EvidenceStateTransitionEdge_node(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type EvidenceStateTransitionEdge", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _EvidenceStateTransitionConnection_pageInfo(ctx context.Context, field graphql.CollectedField, obj *types.EvidenceStateTransitionConnection) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_EvidenceStateTransitionConnection_pageInfo(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.PageInfo, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*types.PageInfo) + fc.Result = res + return ec.marshalNPageInfo2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐPageInfo(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_EvidenceStateTransitionConnection_pageInfo(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "EvidenceStateTransitionConnection", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "hasNextPage": + return ec.fieldContext_PageInfo_hasNextPage(ctx, field) + case "hasPreviousPage": + return ec.fieldContext_PageInfo_hasPreviousPage(ctx, field) + case "startCursor": + return ec.fieldContext_PageInfo_startCursor(ctx, field) + case "endCursor": + return ec.fieldContext_PageInfo_endCursor(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type PageInfo", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _EvidenceStateTransitionEdge_cursor(ctx context.Context, field graphql.CollectedField, obj *types.EvidenceStateTransitionEdge) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_EvidenceStateTransitionEdge_cursor(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Cursor, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(page.CursorKey) + fc.Result = res + return ec.marshalNCursorKey2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_EvidenceStateTransitionEdge_cursor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "EvidenceStateTransitionEdge", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type CursorKey does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _EvidenceStateTransitionEdge_node(ctx context.Context, field graphql.CollectedField, obj *types.EvidenceStateTransitionEdge) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_EvidenceStateTransitionEdge_node(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.Node, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*types.EvidenceStateTransition) + fc.Result = res + return ec.marshalNEvidenceStateTransition2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceStateTransition(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_EvidenceStateTransitionEdge_node(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "EvidenceStateTransitionEdge", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_EvidenceStateTransition_id(ctx, field) + case "fromState": + return ec.fieldContext_EvidenceStateTransition_fromState(ctx, field) + case "toState": + return ec.fieldContext_EvidenceStateTransition_toState(ctx, field) + case "reason": + return ec.fieldContext_EvidenceStateTransition_reason(ctx, field) + case "createdAt": + return ec.fieldContext_EvidenceStateTransition_createdAt(ctx, field) + case "updatedAt": + return ec.fieldContext_EvidenceStateTransition_updatedAt(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type EvidenceStateTransition", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _Framework_id(ctx context.Context, field graphql.CollectedField, obj *types.Framework) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Framework_id(ctx, field) if err != nil { @@ -7986,27 +8651,58 @@ func (ec *executionContext) _Evidence(ctx context.Context, sel ast.SelectionSet, case "id": out.Values[i] = ec._Evidence_id(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "fileUrl": out.Values[i] = ec._Evidence_fileUrl(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "state": out.Values[i] = ec._Evidence_state(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } + case "stateTransisions": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + res = ec._Evidence_stateTransisions(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) case "createdAt": out.Values[i] = ec._Evidence_createdAt(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "updatedAt": out.Values[i] = ec._Evidence_updatedAt(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } default: panic("unknown field " + strconv.Quote(field.Name)) @@ -8119,6 +8815,152 @@ func (ec *executionContext) _EvidenceEdge(ctx context.Context, sel ast.Selection return out } +var evidenceStateTransitionImplementors = []string{"EvidenceStateTransition"} + +func (ec *executionContext) _EvidenceStateTransition(ctx context.Context, sel ast.SelectionSet, obj *types.EvidenceStateTransition) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, evidenceStateTransitionImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("EvidenceStateTransition") + case "id": + out.Values[i] = ec._EvidenceStateTransition_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "fromState": + out.Values[i] = ec._EvidenceStateTransition_fromState(ctx, field, obj) + case "toState": + out.Values[i] = ec._EvidenceStateTransition_toState(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "reason": + out.Values[i] = ec._EvidenceStateTransition_reason(ctx, field, obj) + case "createdAt": + out.Values[i] = ec._EvidenceStateTransition_createdAt(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "updatedAt": + out.Values[i] = ec._EvidenceStateTransition_updatedAt(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var evidenceStateTransitionConnectionImplementors = []string{"EvidenceStateTransitionConnection"} + +func (ec *executionContext) _EvidenceStateTransitionConnection(ctx context.Context, sel ast.SelectionSet, obj *types.EvidenceStateTransitionConnection) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, evidenceStateTransitionConnectionImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("EvidenceStateTransitionConnection") + case "edges": + out.Values[i] = ec._EvidenceStateTransitionConnection_edges(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "pageInfo": + out.Values[i] = ec._EvidenceStateTransitionConnection_pageInfo(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var evidenceStateTransitionEdgeImplementors = []string{"EvidenceStateTransitionEdge"} + +func (ec *executionContext) _EvidenceStateTransitionEdge(ctx context.Context, sel ast.SelectionSet, obj *types.EvidenceStateTransitionEdge) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, evidenceStateTransitionEdgeImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("EvidenceStateTransitionEdge") + case "cursor": + out.Values[i] = ec._EvidenceStateTransitionEdge_cursor(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "node": + out.Values[i] = ec._EvidenceStateTransitionEdge_node(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var frameworkImplementors = []string{"Framework", "Node"} func (ec *executionContext) _Framework(ctx context.Context, sel ast.SelectionSet, obj *types.Framework) graphql.Marshaler { @@ -9820,6 +10662,78 @@ func (ec *executionContext) marshalNEvidenceState2githubᚗcomᚋgetproboᚋprob return v } +func (ec *executionContext) marshalNEvidenceStateTransition2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceStateTransition(ctx context.Context, sel ast.SelectionSet, v *types.EvidenceStateTransition) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._EvidenceStateTransition(ctx, sel, v) +} + +func (ec *executionContext) marshalNEvidenceStateTransitionConnection2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceStateTransitionConnection(ctx context.Context, sel ast.SelectionSet, v types.EvidenceStateTransitionConnection) graphql.Marshaler { + return ec._EvidenceStateTransitionConnection(ctx, sel, &v) +} + +func (ec *executionContext) marshalNEvidenceStateTransitionConnection2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceStateTransitionConnection(ctx context.Context, sel ast.SelectionSet, v *types.EvidenceStateTransitionConnection) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._EvidenceStateTransitionConnection(ctx, sel, v) +} + +func (ec *executionContext) marshalNEvidenceStateTransitionEdge2ᚕᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceStateTransitionEdgeᚄ(ctx context.Context, sel ast.SelectionSet, v []*types.EvidenceStateTransitionEdge) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNEvidenceStateTransitionEdge2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceStateTransitionEdge(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalNEvidenceStateTransitionEdge2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceStateTransitionEdge(ctx context.Context, sel ast.SelectionSet, v *types.EvidenceStateTransitionEdge) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._EvidenceStateTransitionEdge(ctx, sel, v) +} + func (ec *executionContext) marshalNFramework2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐFramework(ctx context.Context, sel ast.SelectionSet, v *types.Framework) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { @@ -10559,6 +11473,22 @@ func (ec *executionContext) marshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋprobo return res } +func (ec *executionContext) unmarshalOEvidenceState2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceState(ctx context.Context, v any) (*types.EvidenceState, error) { + if v == nil { + return nil, nil + } + var res = new(types.EvidenceState) + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOEvidenceState2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceState(ctx context.Context, sel ast.SelectionSet, v *types.EvidenceState) graphql.Marshaler { + if v == nil { + return graphql.Null + } + return v +} + func (ec *executionContext) unmarshalOInt2ᚖint(ctx context.Context, v any) (*int, error) { if v == nil { return nil, nil diff --git a/pkg/api/console/v1/types/evidence_state_transition.go b/pkg/api/console/v1/types/evidence_state_transition.go new file mode 100644 index 000000000..dd5fe1713 --- /dev/null +++ b/pkg/api/console/v1/types/evidence_state_transition.go @@ -0,0 +1,59 @@ +// 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 types + +import ( + "github.com/getprobo/probo/pkg/probo/coredata" + "github.com/getprobo/probo/pkg/probo/coredata/page" +) + +func NewEvidenceStateTransitionConnection( + p *page.Page[*coredata.EvidenceStateTransition], +) *EvidenceStateTransitionConnection { + var edges = make([]*EvidenceStateTransitionEdge, len(p.Data)) + + for i := range edges { + edges[i] = NewEvidenceStateTransitionEdge(p.Data[i]) + } + + return &EvidenceStateTransitionConnection{ + Edges: edges, + PageInfo: NewPageInfo(p), + } +} + +func NewEvidenceStateTransitionEdge(est *coredata.EvidenceStateTransition) *EvidenceStateTransitionEdge { + return &EvidenceStateTransitionEdge{ + Cursor: est.CursorKey(), + Node: NewEvidenceStateTransition(est), + } +} + +func NewEvidenceStateTransition(est *coredata.EvidenceStateTransition) *EvidenceStateTransition { + var fromState *EvidenceState + if est.FromState != nil { + val := EvidenceState((*est.FromState).String()) + fromState = &val + } + + return &EvidenceStateTransition{ + ID: est.ID, + FromState: fromState, + ToState: EvidenceState(est.ToState.String()), + Reason: est.Reason, + CreatedAt: est.CreatedAt, + UpdatedAt: est.UpdatedAt, + } +} diff --git a/pkg/api/console/v1/types/types.go b/pkg/api/console/v1/types/types.go index 5896bff00..4d1137023 100644 --- a/pkg/api/console/v1/types/types.go +++ b/pkg/api/console/v1/types/types.go @@ -61,11 +61,12 @@ type ControlStateTransitionEdge struct { } type Evidence struct { - ID gid.GID `json:"id"` - FileURL string `json:"fileUrl"` - State EvidenceState `json:"state"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` + ID gid.GID `json:"id"` + FileURL string `json:"fileUrl"` + State EvidenceState `json:"state"` + StateTransisions *EvidenceStateTransitionConnection `json:"stateTransisions"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` } func (Evidence) IsNode() {} @@ -81,6 +82,25 @@ type EvidenceEdge struct { Node *Evidence `json:"node"` } +type EvidenceStateTransition struct { + ID gid.GID `json:"id"` + FromState *EvidenceState `json:"fromState,omitempty"` + ToState EvidenceState `json:"toState"` + Reason *string `json:"reason,omitempty"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type EvidenceStateTransitionConnection struct { + Edges []*EvidenceStateTransitionEdge `json:"edges"` + PageInfo *PageInfo `json:"pageInfo"` +} + +type EvidenceStateTransitionEdge struct { + Cursor page.CursorKey `json:"cursor"` + Node *EvidenceStateTransition `json:"node"` +} + type Framework struct { ID gid.GID `json:"id"` Name string `json:"name"` diff --git a/pkg/api/console/v1/v1_resolver.go b/pkg/api/console/v1/v1_resolver.go index 96a4ce1e3..eedc0d48c 100644 --- a/pkg/api/console/v1/v1_resolver.go +++ b/pkg/api/console/v1/v1_resolver.go @@ -40,6 +40,18 @@ func (r *controlResolver) Tasks(ctx context.Context, obj *types.Control, first * return types.NewTaskConnection(page), nil } +// StateTransisions is the resolver for the stateTransisions field. +func (r *evidenceResolver) StateTransisions(ctx context.Context, obj *types.Evidence, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.EvidenceStateTransitionConnection, error) { + cursor := types.NewCursor(first, after, last, before) + + page, err := r.svc.ListEvidenceStateTransitions(ctx, obj.ID, cursor) + if err != nil { + return nil, fmt.Errorf("cannot list evidence state transitions: %w", err) + } + + return types.NewEvidenceStateTransitionConnection(page), nil +} + // 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) (*types.ControlConnection, error) { cursor := types.NewCursor(first, after, last, before) @@ -131,6 +143,9 @@ func (r *taskResolver) Evidences(ctx context.Context, obj *types.Task, first *in // Control returns schema.ControlResolver implementation. func (r *Resolver) Control() schema.ControlResolver { return &controlResolver{r} } +// Evidence returns schema.EvidenceResolver implementation. +func (r *Resolver) Evidence() schema.EvidenceResolver { return &evidenceResolver{r} } + // Framework returns schema.FrameworkResolver implementation. func (r *Resolver) Framework() schema.FrameworkResolver { return &frameworkResolver{r} } @@ -144,6 +159,7 @@ func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} } func (r *Resolver) Task() schema.TaskResolver { return &taskResolver{r} } type controlResolver struct{ *Resolver } +type evidenceResolver struct{ *Resolver } type frameworkResolver struct{ *Resolver } type organizationResolver struct{ *Resolver } type queryResolver struct{ *Resolver } diff --git a/pkg/probo/probo.go b/pkg/probo/probo.go index 1d1f02fa3..abf41dd2b 100644 --- a/pkg/probo/probo.go +++ b/pkg/probo/probo.go @@ -284,3 +284,30 @@ func (s Service) ListTaskStateTransitions( return page.NewPage(taskStateTransitions, cursor), nil } + +func (s Service) ListEvidenceStateTransitions( + ctx context.Context, + evidenceID gid.GID, + cursor *page.Cursor, +) (*page.Page[*coredata.EvidenceStateTransition], error) { + var evidenceStateTransitions coredata.EvidenceStateTransitions + + err := s.pg.WithConn( + ctx, + func(conn pg.Conn) error { + return evidenceStateTransitions.LoadByEvidenceID( + ctx, + conn, + s.scope, + evidenceID, + cursor, + ) + }, + ) + + if err != nil { + return nil, err + } + + return page.NewPage(evidenceStateTransitions, cursor), nil +}