From a4b497ce98b67e12e0b96a0eb54b6b8f62c81602 Mon Sep 17 00:00:00 2001 From: gearnode Date: Tue, 28 Jan 2025 16:16:55 -0800 Subject: [PATCH] Add evidence coredata object Signed-off-by: gearnode --- pkg/api/console/v1/schema.graphql | 25 + pkg/api/console/v1/schema/schema.go | 835 +++++++++++++++++- pkg/api/console/v1/types/evidence.go | 49 + pkg/api/console/v1/types/types.go | 31 +- pkg/api/console/v1/v1_resolver.go | 16 + pkg/probo/coredata/evidence.go | 80 +- .../coredata/migrations/20250128T151500Z.sql | 7 + pkg/probo/probo.go | 26 + 8 files changed, 1057 insertions(+), 12 deletions(-) create mode 100644 pkg/api/console/v1/types/evidence.go create mode 100644 pkg/probo/coredata/migrations/20250128T151500Z.sql diff --git a/pkg/api/console/v1/schema.graphql b/pkg/api/console/v1/schema.graphql index 4189c1594..d651695af 100644 --- a/pkg/api/console/v1/schema.graphql +++ b/pkg/api/console/v1/schema.graphql @@ -136,6 +136,31 @@ type Task implements Node { id: ID! name: String! description: String! + + evidences( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): EvidenceConnection! @goField(forceResolver: true) + + createdAt: Datetime! + updatedAt: Datetime! +} + +type EvidenceConnection { + edges: [EvidenceEdge!]! + pageInfo: PageInfo! +} + +type EvidenceEdge { + cursor: CursorKey! + node: Evidence! +} + +type Evidence implements Node { + id: ID! + fileUrl: String! createdAt: Datetime! updatedAt: Datetime! } diff --git a/pkg/api/console/v1/schema/schema.go b/pkg/api/console/v1/schema/schema.go index 65f048c60..5652b3f4f 100644 --- a/pkg/api/console/v1/schema/schema.go +++ b/pkg/api/console/v1/schema/schema.go @@ -45,6 +45,7 @@ type ResolverRoot interface { Framework() FrameworkResolver Organization() OrganizationResolver Query() QueryResolver + Task() TaskResolver } type DirectiveRoot struct { @@ -91,6 +92,23 @@ type ComplexityRoot struct { Node func(childComplexity int) int } + Evidence struct { + CreatedAt func(childComplexity int) int + FileURL func(childComplexity int) int + ID func(childComplexity int) int + UpdatedAt func(childComplexity int) int + } + + EvidenceConnection struct { + Edges func(childComplexity int) int + PageInfo func(childComplexity int) int + } + + EvidenceEdge 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 @@ -132,6 +150,7 @@ type ComplexityRoot struct { Task struct { CreatedAt func(childComplexity int) int Description func(childComplexity int) int + Evidences func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int ID func(childComplexity int) int Name func(childComplexity int) int UpdatedAt func(childComplexity int) int @@ -161,6 +180,9 @@ type OrganizationResolver interface { type QueryResolver interface { Node(ctx context.Context, id gid.GID) (types.Node, error) } +type TaskResolver interface { + Evidences(ctx context.Context, obj *types.Task, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.EvidenceConnection, error) +} type executableSchema struct { schema *ast.Schema @@ -345,6 +367,62 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.ControlStateTransitionEdge.Node(childComplexity), true + case "Evidence.createdAt": + if e.complexity.Evidence.CreatedAt == nil { + break + } + + return e.complexity.Evidence.CreatedAt(childComplexity), true + + case "Evidence.fileUrl": + if e.complexity.Evidence.FileURL == nil { + break + } + + return e.complexity.Evidence.FileURL(childComplexity), true + + case "Evidence.id": + if e.complexity.Evidence.ID == nil { + break + } + + return e.complexity.Evidence.ID(childComplexity), true + + case "Evidence.updatedAt": + if e.complexity.Evidence.UpdatedAt == nil { + break + } + + return e.complexity.Evidence.UpdatedAt(childComplexity), true + + case "EvidenceConnection.edges": + if e.complexity.EvidenceConnection.Edges == nil { + break + } + + return e.complexity.EvidenceConnection.Edges(childComplexity), true + + case "EvidenceConnection.pageInfo": + if e.complexity.EvidenceConnection.PageInfo == nil { + break + } + + return e.complexity.EvidenceConnection.PageInfo(childComplexity), true + + case "EvidenceEdge.cursor": + if e.complexity.EvidenceEdge.Cursor == nil { + break + } + + return e.complexity.EvidenceEdge.Cursor(childComplexity), true + + case "EvidenceEdge.node": + if e.complexity.EvidenceEdge.Node == nil { + break + } + + return e.complexity.EvidenceEdge.Node(childComplexity), true + case "Framework.controls": if e.complexity.Framework.Controls == nil { break @@ -514,6 +592,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Task.Description(childComplexity), true + case "Task.evidences": + if e.complexity.Task.Evidences == nil { + break + } + + args, err := ec.field_Task_evidences_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Task.Evidences(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey)), true + case "Task.id": if e.complexity.Task.ID == nil { break @@ -790,6 +880,31 @@ type Task implements Node { id: ID! name: String! description: String! + + evidences( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): EvidenceConnection! @goField(forceResolver: true) + + createdAt: Datetime! + updatedAt: Datetime! +} + +type EvidenceConnection { + edges: [EvidenceEdge!]! + pageInfo: PageInfo! +} + +type EvidenceEdge { + cursor: CursorKey! + node: Evidence! +} + +type Evidence implements Node { + id: ID! + fileUrl: String! createdAt: Datetime! updatedAt: Datetime! } @@ -1159,6 +1274,83 @@ func (ec *executionContext) field_Query_node_argsID( return zeroVal, nil } +func (ec *executionContext) field_Task_evidences_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Task_evidences_argsFirst(ctx, rawArgs) + if err != nil { + return nil, err + } + args["first"] = arg0 + arg1, err := ec.field_Task_evidences_argsAfter(ctx, rawArgs) + if err != nil { + return nil, err + } + args["after"] = arg1 + arg2, err := ec.field_Task_evidences_argsLast(ctx, rawArgs) + if err != nil { + return nil, err + } + args["last"] = arg2 + arg3, err := ec.field_Task_evidences_argsBefore(ctx, rawArgs) + if err != nil { + return nil, err + } + args["before"] = arg3 + return args, nil +} +func (ec *executionContext) field_Task_evidences_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_Task_evidences_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_Task_evidences_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_Task_evidences_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___Type_enumValues_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -2129,6 +2321,336 @@ func (ec *executionContext) fieldContext_ControlStateTransitionEdge_node(_ conte return fc, nil } +func (ec *executionContext) _Evidence_id(ctx context.Context, field graphql.CollectedField, obj *types.Evidence) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Evidence_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_Evidence_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Evidence", + 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) _Evidence_fileUrl(ctx context.Context, field graphql.CollectedField, obj *types.Evidence) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Evidence_fileUrl(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.FileURL, 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.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Evidence_fileUrl(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Evidence", + 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) _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 { + 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_Evidence_createdAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Evidence", + 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) _Evidence_updatedAt(ctx context.Context, field graphql.CollectedField, obj *types.Evidence) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Evidence_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_Evidence_updatedAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Evidence", + 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) _EvidenceConnection_edges(ctx context.Context, field graphql.CollectedField, obj *types.EvidenceConnection) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_EvidenceConnection_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.EvidenceEdge) + fc.Result = res + return ec.marshalNEvidenceEdge2ᚕᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceEdgeᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_EvidenceConnection_edges(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "EvidenceConnection", + 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_EvidenceEdge_cursor(ctx, field) + case "node": + return ec.fieldContext_EvidenceEdge_node(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type EvidenceEdge", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _EvidenceConnection_pageInfo(ctx context.Context, field graphql.CollectedField, obj *types.EvidenceConnection) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_EvidenceConnection_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_EvidenceConnection_pageInfo(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "EvidenceConnection", + 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) _EvidenceEdge_cursor(ctx context.Context, field graphql.CollectedField, obj *types.EvidenceEdge) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_EvidenceEdge_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_EvidenceEdge_cursor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "EvidenceEdge", + 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) _EvidenceEdge_node(ctx context.Context, field graphql.CollectedField, obj *types.EvidenceEdge) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_EvidenceEdge_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.Evidence) + fc.Result = res + return ec.marshalNEvidence2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidence(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_EvidenceEdge_node(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "EvidenceEdge", + 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_Evidence_id(ctx, field) + case "fileUrl": + return ec.fieldContext_Evidence_fileUrl(ctx, field) + case "createdAt": + return ec.fieldContext_Evidence_createdAt(ctx, field) + case "updatedAt": + return ec.fieldContext_Evidence_updatedAt(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Evidence", 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 { @@ -3165,6 +3687,55 @@ func (ec *executionContext) fieldContext_Task_description(_ context.Context, fie return fc, nil } +func (ec *executionContext) _Task_evidences(ctx context.Context, field graphql.CollectedField, obj *types.Task) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Task_evidences(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.Task().Evidences(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.EvidenceConnection) + fc.Result = res + return ec.marshalNEvidenceConnection2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceConnection(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Task_evidences(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Task", + 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_EvidenceConnection_edges(ctx, field) + case "pageInfo": + return ec.fieldContext_EvidenceConnection_pageInfo(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type EvidenceConnection", field.Name) + }, + } + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Task_evidences_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Task_createdAt(ctx context.Context, field graphql.CollectedField, obj *types.Task) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Task_createdAt(ctx, field) if err != nil { @@ -3410,6 +3981,8 @@ func (ec *executionContext) fieldContext_TaskEdge_node(_ context.Context, field return ec.fieldContext_Task_name(ctx, field) case "description": return ec.fieldContext_Task_description(ctx, field) + case "evidences": + return ec.fieldContext_Task_evidences(ctx, field) case "createdAt": return ec.fieldContext_Task_createdAt(ctx, field) case "updatedAt": @@ -5008,6 +5581,13 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj return graphql.Null } return ec._Task(ctx, sel, obj) + case types.Evidence: + return ec._Evidence(ctx, sel, &obj) + case *types.Evidence: + if obj == nil { + return graphql.Null + } + return ec._Evidence(ctx, sel, obj) default: panic(fmt.Errorf("unexpected type %T", obj)) } @@ -5377,6 +5957,148 @@ func (ec *executionContext) _ControlStateTransitionEdge(ctx context.Context, sel return out } +var evidenceImplementors = []string{"Evidence", "Node"} + +func (ec *executionContext) _Evidence(ctx context.Context, sel ast.SelectionSet, obj *types.Evidence) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, evidenceImplementors) + + 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("Evidence") + case "id": + out.Values[i] = ec._Evidence_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "fileUrl": + out.Values[i] = ec._Evidence_fileUrl(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "createdAt": + out.Values[i] = ec._Evidence_createdAt(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "updatedAt": + out.Values[i] = ec._Evidence_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 evidenceConnectionImplementors = []string{"EvidenceConnection"} + +func (ec *executionContext) _EvidenceConnection(ctx context.Context, sel ast.SelectionSet, obj *types.EvidenceConnection) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, evidenceConnectionImplementors) + + 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("EvidenceConnection") + case "edges": + out.Values[i] = ec._EvidenceConnection_edges(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "pageInfo": + out.Values[i] = ec._EvidenceConnection_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 evidenceEdgeImplementors = []string{"EvidenceEdge"} + +func (ec *executionContext) _EvidenceEdge(ctx context.Context, sel ast.SelectionSet, obj *types.EvidenceEdge) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, evidenceEdgeImplementors) + + 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("EvidenceEdge") + case "cursor": + out.Values[i] = ec._EvidenceEdge_cursor(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "node": + out.Values[i] = ec._EvidenceEdge_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 { @@ -5769,27 +6491,58 @@ func (ec *executionContext) _Task(ctx context.Context, sel ast.SelectionSet, obj case "id": out.Values[i] = ec._Task_id(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "name": out.Values[i] = ec._Task_name(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "description": out.Values[i] = ec._Task_description(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } + case "evidences": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + res = ec._Task_evidences(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._Task_createdAt(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "updatedAt": out.Values[i] = ec._Task_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)) @@ -6427,6 +7180,78 @@ func (ec *executionContext) marshalNDatetime2timeᚐTime(ctx context.Context, se return res } +func (ec *executionContext) marshalNEvidence2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidence(ctx context.Context, sel ast.SelectionSet, v *types.Evidence) 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._Evidence(ctx, sel, v) +} + +func (ec *executionContext) marshalNEvidenceConnection2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceConnection(ctx context.Context, sel ast.SelectionSet, v types.EvidenceConnection) graphql.Marshaler { + return ec._EvidenceConnection(ctx, sel, &v) +} + +func (ec *executionContext) marshalNEvidenceConnection2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceConnection(ctx context.Context, sel ast.SelectionSet, v *types.EvidenceConnection) 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._EvidenceConnection(ctx, sel, v) +} + +func (ec *executionContext) marshalNEvidenceEdge2ᚕᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceEdgeᚄ(ctx context.Context, sel ast.SelectionSet, v []*types.EvidenceEdge) 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.marshalNEvidenceEdge2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceEdge(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) marshalNEvidenceEdge2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceEdge(ctx context.Context, sel ast.SelectionSet, v *types.EvidenceEdge) 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._EvidenceEdge(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)) { diff --git a/pkg/api/console/v1/types/evidence.go b/pkg/api/console/v1/types/evidence.go new file mode 100644 index 000000000..e578fe465 --- /dev/null +++ b/pkg/api/console/v1/types/evidence.go @@ -0,0 +1,49 @@ +// 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 NewEvidenceConnection(p *page.Page[*coredata.Evidence]) *EvidenceConnection { + var edges = make([]*EvidenceEdge, len(p.Data)) + + for i := range edges { + edges[i] = NewEvidenceEdge(p.Data[i]) + } + + return &EvidenceConnection{ + Edges: edges, + PageInfo: NewPageInfo(p), + } +} + +func NewEvidenceEdge(e *coredata.Evidence) *EvidenceEdge { + return &EvidenceEdge{ + Cursor: e.CursorKey(), + Node: NewEvidence(e), + } +} + +func NewEvidence(c *coredata.Evidence) *Evidence { + return &Evidence{ + ID: c.ID, + FileURL: "", + CreatedAt: c.CreatedAt, + UpdatedAt: c.UpdatedAt, + } +} diff --git a/pkg/api/console/v1/types/types.go b/pkg/api/console/v1/types/types.go index fe57e9d81..1d4a12487 100644 --- a/pkg/api/console/v1/types/types.go +++ b/pkg/api/console/v1/types/types.go @@ -60,6 +60,26 @@ type ControlStateTransitionEdge struct { Node *ControlStateTransition `json:"node"` } +type Evidence struct { + ID gid.GID `json:"id"` + FileURL string `json:"fileUrl"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +func (Evidence) IsNode() {} +func (this Evidence) GetID() gid.GID { return this.ID } + +type EvidenceConnection struct { + Edges []*EvidenceEdge `json:"edges"` + PageInfo *PageInfo `json:"pageInfo"` +} + +type EvidenceEdge struct { + Cursor page.CursorKey `json:"cursor"` + Node *Evidence `json:"node"` +} + type Framework struct { ID gid.GID `json:"id"` Name string `json:"name"` @@ -104,11 +124,12 @@ type Query struct { } type Task struct { - ID gid.GID `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` + ID gid.GID `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Evidences *EvidenceConnection `json:"evidences"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` } func (Task) IsNode() {} diff --git a/pkg/api/console/v1/v1_resolver.go b/pkg/api/console/v1/v1_resolver.go index 4d8ebc2d4..743f803d4 100644 --- a/pkg/api/console/v1/v1_resolver.go +++ b/pkg/api/console/v1/v1_resolver.go @@ -80,6 +80,18 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error return nil, gqlerror.Errorf("node %q not found", id) } +// Evidences is the resolver for the evidences field. +func (r *taskResolver) Evidences(ctx context.Context, obj *types.Task, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.EvidenceConnection, error) { + cursor := types.NewCursor(first, after, last, before) + + page, err := r.svc.ListTaskEvidences(ctx, obj.ID, cursor) + if err != nil { + return nil, fmt.Errorf("cannot list organization frameworks: %w", err) + } + + return types.NewEvidenceConnection(page), nil +} + // Control returns schema.ControlResolver implementation. func (r *Resolver) Control() schema.ControlResolver { return &controlResolver{r} } @@ -92,7 +104,11 @@ func (r *Resolver) Organization() schema.OrganizationResolver { return &organiza // Query returns schema.QueryResolver implementation. func (r *Resolver) Query() schema.QueryResolver { return &queryResolver{r} } +// Task returns schema.TaskResolver implementation. +func (r *Resolver) Task() schema.TaskResolver { return &taskResolver{r} } + type controlResolver struct{ *Resolver } type frameworkResolver struct{ *Resolver } type organizationResolver struct{ *Resolver } type queryResolver struct{ *Resolver } +type taskResolver struct{ *Resolver } diff --git a/pkg/probo/coredata/evidence.go b/pkg/probo/coredata/evidence.go index 9f18a797e..95090e778 100644 --- a/pkg/probo/coredata/evidence.go +++ b/pkg/probo/coredata/evidence.go @@ -15,17 +15,93 @@ package coredata import ( + "context" + "fmt" + "maps" "time" "github.com/getprobo/probo/pkg/probo/coredata/gid" + "github.com/getprobo/probo/pkg/probo/coredata/page" + "github.com/jackc/pgx/v5" + "go.gearno.de/crypto/uuid" + "go.gearno.de/kit/pg" ) type ( Evidence struct { ID gid.GID - TaskID string - ContentID string + TaskID gid.GID + ObjectKey string CreatedAt time.Time UpdatedAt time.Time } + + Evidences []*Evidence ) + +func (e Evidence) CursorKey() page.CursorKey { + return page.NewCursorKey(uuid.UUID(e.ID), e.CreatedAt) +} + +func (e *Evidence) scan(r pgx.Row) error { + return r.Scan( + &e.ID, + &e.TaskID, + &e.ObjectKey, + &e.CreatedAt, + &e.UpdatedAt, + ) +} + +func (e *Evidences) LoadByTaskID( + ctx context.Context, + conn pg.Conn, + scope *Scope, + taskID gid.GID, + cursor *page.Cursor, +) error { + q := ` +SELECT + id, + task_id, + object_key, + created_at, + updated_at +FROM + evidences +WHERE + %s + AND task_id = @task_id + AND %s +` + + q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) + + args := pgx.NamedArgs{"task_id": taskID} + maps.Copy(args, scope.SQLArguments()) + maps.Copy(args, cursor.SQLArguments()) + + r, err := conn.Query(ctx, q, args) + if err != nil { + return err + } + defer r.Close() + + evidences := Evidences{} + for r.Next() { + evidence := &Evidence{} + if err := evidence.scan(r); err != nil { + return err + } + + evidences = append(evidences, evidence) + } + + if err := r.Err(); err != nil { + return err + } + + *e = evidences + + return nil +} diff --git a/pkg/probo/coredata/migrations/20250128T151500Z.sql b/pkg/probo/coredata/migrations/20250128T151500Z.sql new file mode 100644 index 000000000..e3dcea8be --- /dev/null +++ b/pkg/probo/coredata/migrations/20250128T151500Z.sql @@ -0,0 +1,7 @@ +CREATE TABLE evidences ( + id TEXT PRIMARY KEY, + task_id TEXT REFERENCES tasks(id) NOT NULL, + object_key TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE NOT NULL, + updated_at TIMESTAMP WITH TIME ZONE NOT NULL +); diff --git a/pkg/probo/probo.go b/pkg/probo/probo.go index 4e61ce36c..24aeaa063 100644 --- a/pkg/probo/probo.go +++ b/pkg/probo/probo.go @@ -177,3 +177,29 @@ func (s *Service) ListControlStateTransitions( return page.NewPage(controlStateTransitions, cursor), nil } +func (s *Service) ListTaskEvidences( + ctx context.Context, + taskID gid.GID, + cursor *page.Cursor, +) (*page.Page[*coredata.Evidence], error) { + var evidences coredata.Evidences + + err := s.pg.WithConn( + ctx, + func(conn pg.Conn) error { + return evidences.LoadByTaskID( + ctx, + conn, + s.scope, + taskID, + cursor, + ) + }, + ) + + if err != nil { + return nil, err + } + + return page.NewPage(evidences, cursor), nil +}