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 Void
scalar Datetime
scalar Upload
interface Node {
id: ID!
@@ -74,6 +75,7 @@ type Control implements Node {
id: ID!
name: String!
description: String!
state: String!
tasks(
first: Int

View File

@@ -56,6 +56,7 @@ type ComplexityRoot struct {
Description func(childComplexity int) int
ID 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
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
case "Control.state":
if e.complexity.Control.State == nil {
break
}
return e.complexity.Control.State(childComplexity), true
case "Control.tasks":
if e.complexity.Control.Tasks == nil {
break
@@ -542,6 +550,7 @@ var sources = []*ast.Source{
scalar CursorKey
scalar Void
scalar Datetime
scalar Upload
interface Node {
id: ID!
@@ -609,6 +618,7 @@ type Control implements Node {
id: ID!
name: String!
description: String!
state: String!
tasks(
first: Int
@@ -1094,6 +1104,44 @@ func (ec *executionContext) fieldContext_Control_description(_ context.Context,
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) {
fc, err := ec.fieldContext_Control_tasks(ctx, field)
if err != nil {
@@ -1388,6 +1436,8 @@ func (ec *executionContext) fieldContext_ControlEdge_node(_ context.Context, fie
return ec.fieldContext_Control_name(ctx, field)
case "description":
return ec.fieldContext_Control_description(ctx, field)
case "state":
return ec.fieldContext_Control_state(ctx, field)
case "tasks":
return ec.fieldContext_Control_tasks(ctx, field)
case "createdAt":
@@ -4275,6 +4325,11 @@ func (ec *executionContext) _Control(ctx context.Context, sel ast.SelectionSet,
if out.Values[i] == graphql.Null {
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":
field := field

View File

@@ -44,6 +44,7 @@ func NewControl(c *coredata.Control) *Control {
ID: c.ID,
Name: c.Name,
Description: c.Description,
State: string(c.State),
CreatedAt: c.CreatedAt,
UpdatedAt: c.UpdatedAt,
}

View File

@@ -18,6 +18,7 @@ type Control struct {
ID gid.GID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
State string `json:"state"`
Tasks *TaskConnection `json:"tasks"`
CreatedAt time.Time `json:"createdAt"`
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.
func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) {
switch id.EntityType() {
case coredata.OrganizationEntityType:
organization, err := r.svc.GetOrganization(ctx, id)

View File

@@ -34,6 +34,7 @@ type (
FrameworkID string
Name string
Description string
State ControlState
ContentRef string
CreatedAt time.Time
UpdatedAt time.Time
@@ -52,6 +53,7 @@ func (c *Control) scan(r pgx.Row) error {
&c.FrameworkID,
&c.Name,
&c.Description,
&c.State,
&c.ContentRef,
&c.CreatedAt,
&c.UpdatedAt,
@@ -66,19 +68,34 @@ func (c *Controls) LoadByFrameworkID(
cursor *page.Cursor,
) error {
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
id,
framework_id,
name,
description,
cs.to_state AS state,
content_ref,
created_at,
updated_at
FROM
controls
INNER JOIN
control_states cs ON cs.control_id = controls.id
WHERE
%s
AND framework_id = @framework_id
AND cs.rank = 1
AND %s
`
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
);