From a054fae323cb07ef99fb794cdf537a19c6b2b9aa Mon Sep 17 00:00:00 2001 From: gearnode Date: Thu, 30 Jan 2025 15:00:24 -0800 Subject: [PATCH] Add state to task Signed-off-by: gearnode --- pkg/api/console/v1/schema.graphql | 5 ++ pkg/api/console/v1/schema/schema.go | 70 ++++++++++++++++++ pkg/api/console/v1/types/task.go | 1 + pkg/api/console/v1/types/types.go | 42 +++++++++++ .../coredata/migrations/20250130T145600Z.sql | 6 ++ pkg/probo/coredata/task.go | 4 + pkg/probo/coredata/task_state.go | 74 +++++++++++++++++++ 7 files changed, 202 insertions(+) create mode 100644 pkg/probo/coredata/migrations/20250130T145600Z.sql create mode 100644 pkg/probo/coredata/task_state.go diff --git a/pkg/api/console/v1/schema.graphql b/pkg/api/console/v1/schema.graphql index 336d86f46..719428aa4 100644 --- a/pkg/api/console/v1/schema.graphql +++ b/pkg/api/console/v1/schema.graphql @@ -20,6 +20,10 @@ enum ControlState { IMPLEMENTED } +enum TaskState { + TODO + DONE +} type PageInfo { hasNextPage: Boolean! @@ -186,6 +190,7 @@ type Task implements Node { id: ID! name: String! description: String! + state: TaskState! evidences( first: Int diff --git a/pkg/api/console/v1/schema/schema.go b/pkg/api/console/v1/schema/schema.go index ec99bc3a8..61dddac60 100644 --- a/pkg/api/console/v1/schema/schema.go +++ b/pkg/api/console/v1/schema/schema.go @@ -174,6 +174,7 @@ type ComplexityRoot struct { 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 + State func(childComplexity int) int UpdatedAt func(childComplexity int) int } @@ -752,6 +753,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Task.Name(childComplexity), true + case "Task.state": + if e.complexity.Task.State == nil { + break + } + + return e.complexity.Task.State(childComplexity), true + case "Task.updatedAt": if e.complexity.Task.UpdatedAt == nil { break @@ -955,6 +963,12 @@ enum ControlState { } +enum TaskState { + TODO + DONE +} + + type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! @@ -1120,6 +1134,7 @@ type Task implements Node { id: ID! name: String! description: String! + state: TaskState! evidences( first: Int @@ -4589,6 +4604,44 @@ func (ec *executionContext) fieldContext_Task_description(_ context.Context, fie return fc, nil } +func (ec *executionContext) _Task_state(ctx context.Context, field graphql.CollectedField, obj *types.Task) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Task_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.(types.TaskState) + fc.Result = res + return ec.marshalNTaskState2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐTaskState(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Task_state(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Task", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type TaskState does not have child fields") + }, + } + 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 { @@ -4883,6 +4936,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 "state": + return ec.fieldContext_Task_state(ctx, field) case "evidences": return ec.fieldContext_Task_evidences(ctx, field) case "createdAt": @@ -7963,6 +8018,11 @@ func (ec *executionContext) _Task(ctx context.Context, sel ast.SelectionSet, obj if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } + case "state": + out.Values[i] = ec._Task_state(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } case "evidences": field := field @@ -9152,6 +9212,16 @@ func (ec *executionContext) marshalNTaskEdge2ᚖgithubᚗcomᚋgetproboᚋprobo return ec._TaskEdge(ctx, sel, v) } +func (ec *executionContext) unmarshalNTaskState2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐTaskState(ctx context.Context, v any) (types.TaskState, error) { + var res types.TaskState + err := res.UnmarshalGQL(v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNTaskState2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐTaskState(ctx context.Context, sel ast.SelectionSet, v types.TaskState) graphql.Marshaler { + return v +} + func (ec *executionContext) marshalNVendor2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐVendor(ctx context.Context, sel ast.SelectionSet, v *types.Vendor) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { diff --git a/pkg/api/console/v1/types/task.go b/pkg/api/console/v1/types/task.go index 9a416dd74..36a9afc6f 100644 --- a/pkg/api/console/v1/types/task.go +++ b/pkg/api/console/v1/types/task.go @@ -44,6 +44,7 @@ func NewTask(t *coredata.Task) *Task { ID: t.ID, Name: t.Name, Description: t.Description, + State: TaskState(t.State.String()), CreatedAt: t.CreatedAt, UpdatedAt: t.UpdatedAt, } diff --git a/pkg/api/console/v1/types/types.go b/pkg/api/console/v1/types/types.go index ec4dfbb80..7800c9046 100644 --- a/pkg/api/console/v1/types/types.go +++ b/pkg/api/console/v1/types/types.go @@ -151,6 +151,7 @@ type Task struct { ID gid.GID `json:"id"` Name string `json:"name"` Description string `json:"description"` + State TaskState `json:"state"` Evidences *EvidenceConnection `json:"evidences"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` @@ -233,3 +234,44 @@ func (e *ControlState) UnmarshalGQL(v any) error { func (e ControlState) MarshalGQL(w io.Writer) { fmt.Fprint(w, strconv.Quote(e.String())) } + +type TaskState string + +const ( + TaskStateTodo TaskState = "TODO" + TaskStateDone TaskState = "DONE" +) + +var AllTaskState = []TaskState{ + TaskStateTodo, + TaskStateDone, +} + +func (e TaskState) IsValid() bool { + switch e { + case TaskStateTodo, TaskStateDone: + return true + } + return false +} + +func (e TaskState) String() string { + return string(e) +} + +func (e *TaskState) UnmarshalGQL(v any) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = TaskState(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid TaskState", str) + } + return nil +} + +func (e TaskState) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} diff --git a/pkg/probo/coredata/migrations/20250130T145600Z.sql b/pkg/probo/coredata/migrations/20250130T145600Z.sql new file mode 100644 index 000000000..f18156a66 --- /dev/null +++ b/pkg/probo/coredata/migrations/20250130T145600Z.sql @@ -0,0 +1,6 @@ +CREATE TYPE task_state AS ENUM ( + 'TODO', + 'DONE' +); + +ALTER TABLE tasks ADD COLUMN state task_state NOT NULL; diff --git a/pkg/probo/coredata/task.go b/pkg/probo/coredata/task.go index 8da2d6f05..11b2c691c 100644 --- a/pkg/probo/coredata/task.go +++ b/pkg/probo/coredata/task.go @@ -34,6 +34,7 @@ type ( ControlID gid.GID Name string Description string + State TaskState ContentRef string CreatedAt time.Time UpdatedAt time.Time @@ -52,6 +53,7 @@ func (t *Task) scan(r pgx.Row) error { &t.ControlID, &t.Name, &t.Description, + &t.State, &t.ContentRef, &t.CreatedAt, &t.UpdatedAt, @@ -72,6 +74,7 @@ WITH control_tasks AS ( @control_id AS control_id, t.name, t.description, + t.state, t.content_ref, t.created_at, t.updated_at @@ -89,6 +92,7 @@ SELECT control_id, name, description, + state, content_ref, created_at, updated_at diff --git a/pkg/probo/coredata/task_state.go b/pkg/probo/coredata/task_state.go new file mode 100644 index 000000000..4a8d25c18 --- /dev/null +++ b/pkg/probo/coredata/task_state.go @@ -0,0 +1,74 @@ +// 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 coredata + +import ( + "database/sql/driver" + "fmt" +) + +type ( + TaskState uint8 +) + +const ( + TaskStateTodo TaskState = iota + TaskStateDone +) + +func (ts TaskState) MarshalText() ([]byte, error) { + return []byte(ts.String()), nil +} + +func (ts *TaskState) UnmarshalText(data []byte) error { + val := string(data) + + switch val { + case TaskStateTodo.String(): + *ts = TaskStateTodo + case TaskStateDone.String(): + *ts = TaskStateDone + default: + return fmt.Errorf("invalid TaskState value: %q", val) + } + + return nil +} + +func (ts TaskState) String() string { + var val string + + switch ts { + case TaskStateTodo: + val = "TODO" + case TaskStateDone: + val = "DONE" + } + + return val +} + +func (ts *TaskState) Scan(value any) error { + val, ok := value.(string) + if !ok { + return fmt.Errorf("invalid scan source for TaskState, expected string got %T", value) + } + + return ts.UnmarshalText([]byte(val)) +} + +func (ts TaskState) Value() (driver.Value, error) { + return ts.String(), nil +}