Add control state

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-01-27 18:05:39 +01:00
parent 96355bb914
commit 422c96a250
7 changed files with 92 additions and 1 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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,
} }

View File

@@ -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"`

View File

@@ -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)

View File

@@ -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())

View 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
);