@@ -7,6 +7,7 @@ directive @goField(
|
|||||||
scalar CursorKey
|
scalar CursorKey
|
||||||
scalar Void
|
scalar Void
|
||||||
scalar Datetime
|
scalar Datetime
|
||||||
|
scalar Upload
|
||||||
|
|
||||||
interface Node {
|
interface Node {
|
||||||
id: ID!
|
id: ID!
|
||||||
@@ -74,6 +75,7 @@ type Control implements Node {
|
|||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
description: String!
|
description: String!
|
||||||
|
state: String!
|
||||||
|
|
||||||
tasks(
|
tasks(
|
||||||
first: Int
|
first: Int
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ type ComplexityRoot struct {
|
|||||||
Description func(childComplexity int) int
|
Description func(childComplexity int) int
|
||||||
ID func(childComplexity int) int
|
ID func(childComplexity int) int
|
||||||
Name func(childComplexity int) int
|
Name func(childComplexity int) int
|
||||||
|
State func(childComplexity int) int
|
||||||
Tasks func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
|
Tasks func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
|
||||||
UpdatedAt func(childComplexity int) int
|
UpdatedAt func(childComplexity int) int
|
||||||
}
|
}
|
||||||
@@ -186,6 +187,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||||||
|
|
||||||
return e.complexity.Control.Name(childComplexity), true
|
return e.complexity.Control.Name(childComplexity), true
|
||||||
|
|
||||||
|
case "Control.state":
|
||||||
|
if e.complexity.Control.State == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return e.complexity.Control.State(childComplexity), true
|
||||||
|
|
||||||
case "Control.tasks":
|
case "Control.tasks":
|
||||||
if e.complexity.Control.Tasks == nil {
|
if e.complexity.Control.Tasks == nil {
|
||||||
break
|
break
|
||||||
@@ -542,6 +550,7 @@ var sources = []*ast.Source{
|
|||||||
scalar CursorKey
|
scalar CursorKey
|
||||||
scalar Void
|
scalar Void
|
||||||
scalar Datetime
|
scalar Datetime
|
||||||
|
scalar Upload
|
||||||
|
|
||||||
interface Node {
|
interface Node {
|
||||||
id: ID!
|
id: ID!
|
||||||
@@ -609,6 +618,7 @@ type Control implements Node {
|
|||||||
id: ID!
|
id: ID!
|
||||||
name: String!
|
name: String!
|
||||||
description: String!
|
description: String!
|
||||||
|
state: String!
|
||||||
|
|
||||||
tasks(
|
tasks(
|
||||||
first: Int
|
first: Int
|
||||||
@@ -1094,6 +1104,44 @@ func (ec *executionContext) fieldContext_Control_description(_ context.Context,
|
|||||||
return fc, nil
|
return fc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ec *executionContext) _Control_state(ctx context.Context, field graphql.CollectedField, obj *types.Control) (ret graphql.Marshaler) {
|
||||||
|
fc, err := ec.fieldContext_Control_state(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.State, 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_Control_state(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||||
|
fc = &graphql.FieldContext{
|
||||||
|
Object: "Control",
|
||||||
|
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) _Control_tasks(ctx context.Context, field graphql.CollectedField, obj *types.Control) (ret graphql.Marshaler) {
|
func (ec *executionContext) _Control_tasks(ctx context.Context, field graphql.CollectedField, obj *types.Control) (ret graphql.Marshaler) {
|
||||||
fc, err := ec.fieldContext_Control_tasks(ctx, field)
|
fc, err := ec.fieldContext_Control_tasks(ctx, field)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1388,6 +1436,8 @@ func (ec *executionContext) fieldContext_ControlEdge_node(_ context.Context, fie
|
|||||||
return ec.fieldContext_Control_name(ctx, field)
|
return ec.fieldContext_Control_name(ctx, field)
|
||||||
case "description":
|
case "description":
|
||||||
return ec.fieldContext_Control_description(ctx, field)
|
return ec.fieldContext_Control_description(ctx, field)
|
||||||
|
case "state":
|
||||||
|
return ec.fieldContext_Control_state(ctx, field)
|
||||||
case "tasks":
|
case "tasks":
|
||||||
return ec.fieldContext_Control_tasks(ctx, field)
|
return ec.fieldContext_Control_tasks(ctx, field)
|
||||||
case "createdAt":
|
case "createdAt":
|
||||||
@@ -4275,6 +4325,11 @@ func (ec *executionContext) _Control(ctx context.Context, sel ast.SelectionSet,
|
|||||||
if out.Values[i] == graphql.Null {
|
if out.Values[i] == graphql.Null {
|
||||||
atomic.AddUint32(&out.Invalids, 1)
|
atomic.AddUint32(&out.Invalids, 1)
|
||||||
}
|
}
|
||||||
|
case "state":
|
||||||
|
out.Values[i] = ec._Control_state(ctx, field, obj)
|
||||||
|
if out.Values[i] == graphql.Null {
|
||||||
|
atomic.AddUint32(&out.Invalids, 1)
|
||||||
|
}
|
||||||
case "tasks":
|
case "tasks":
|
||||||
field := field
|
field := field
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ func NewControl(c *coredata.Control) *Control {
|
|||||||
ID: c.ID,
|
ID: c.ID,
|
||||||
Name: c.Name,
|
Name: c.Name,
|
||||||
Description: c.Description,
|
Description: c.Description,
|
||||||
|
State: string(c.State),
|
||||||
CreatedAt: c.CreatedAt,
|
CreatedAt: c.CreatedAt,
|
||||||
UpdatedAt: c.UpdatedAt,
|
UpdatedAt: c.UpdatedAt,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type Control struct {
|
|||||||
ID gid.GID `json:"id"`
|
ID gid.GID `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
|
State string `json:"state"`
|
||||||
Tasks *TaskConnection `json:"tasks"`
|
Tasks *TaskConnection `json:"tasks"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organi
|
|||||||
|
|
||||||
// Node is the resolver for the node field.
|
// Node is the resolver for the node field.
|
||||||
func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) {
|
func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) {
|
||||||
|
|
||||||
switch id.EntityType() {
|
switch id.EntityType() {
|
||||||
case coredata.OrganizationEntityType:
|
case coredata.OrganizationEntityType:
|
||||||
organization, err := r.svc.GetOrganization(ctx, id)
|
organization, err := r.svc.GetOrganization(ctx, id)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ type (
|
|||||||
FrameworkID string
|
FrameworkID string
|
||||||
Name string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
|
State ControlState
|
||||||
ContentRef string
|
ContentRef string
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
@@ -52,6 +53,7 @@ func (c *Control) scan(r pgx.Row) error {
|
|||||||
&c.FrameworkID,
|
&c.FrameworkID,
|
||||||
&c.Name,
|
&c.Name,
|
||||||
&c.Description,
|
&c.Description,
|
||||||
|
&c.State,
|
||||||
&c.ContentRef,
|
&c.ContentRef,
|
||||||
&c.CreatedAt,
|
&c.CreatedAt,
|
||||||
&c.UpdatedAt,
|
&c.UpdatedAt,
|
||||||
@@ -66,19 +68,34 @@ func (c *Controls) LoadByFrameworkID(
|
|||||||
cursor *page.Cursor,
|
cursor *page.Cursor,
|
||||||
) error {
|
) error {
|
||||||
q := `
|
q := `
|
||||||
|
WITH control_states AS (
|
||||||
|
SELECT
|
||||||
|
control_id,
|
||||||
|
to_state,
|
||||||
|
reason,
|
||||||
|
RANK() OVER w
|
||||||
|
FROM
|
||||||
|
control_state_transitions
|
||||||
|
WINDOW
|
||||||
|
w AS (PARTITION BY control_id ORDER BY created_at DESC)
|
||||||
|
)
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id,
|
||||||
framework_id,
|
framework_id,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
|
cs.to_state AS state,
|
||||||
content_ref,
|
content_ref,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
FROM
|
FROM
|
||||||
controls
|
controls
|
||||||
|
INNER JOIN
|
||||||
|
control_states cs ON cs.control_id = controls.id
|
||||||
WHERE
|
WHERE
|
||||||
%s
|
%s
|
||||||
AND framework_id = @framework_id
|
AND framework_id = @framework_id
|
||||||
|
AND cs.rank = 1
|
||||||
AND %s
|
AND %s
|
||||||
`
|
`
|
||||||
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
||||||
|
|||||||
16
pkg/probo/coredata/migrations/20250126T141600Z.sql
Normal file
16
pkg/probo/coredata/migrations/20250126T141600Z.sql
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
CREATE TYPE control_state AS ENUM (
|
||||||
|
'NOT_STARTED',
|
||||||
|
'IN_PROGRESS',
|
||||||
|
'NOT_APPLICABLE',
|
||||||
|
'IMPLEMENTED',
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE control_state_transitions (
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
control_id TEXT REFERENCES controls(id) NOT NULL,
|
||||||
|
from_state control_state,
|
||||||
|
to_state control_state NOT NULL,
|
||||||
|
reason TEXT,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE NOT NULL
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user