Add control importance

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-03-13 08:46:51 +01:00
parent 4ede969b04
commit d4280b1111
16 changed files with 1162 additions and 186 deletions

View File

@@ -29,16 +29,17 @@ import (
type (
Control struct {
ID gid.GID `db:"id"`
FrameworkID gid.GID `db:"framework_id"`
Category string `db:"category"`
Name string `db:"name"`
Description string `db:"description"`
State ControlState `db:"state"`
ContentRef string `db:"content_ref"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
Version int `db:"version"`
ID gid.GID `db:"id"`
FrameworkID gid.GID `db:"framework_id"`
Category string `db:"category"`
Name string `db:"name"`
Description string `db:"description"`
Importance ControlImportance `db:"importance"`
State ControlState `db:"state"`
ContentRef string `db:"content_ref"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
Version int `db:"version"`
}
Controls []*Control
@@ -49,6 +50,7 @@ type (
Description *string
Category *string
State *ControlState
Importance *ControlImportance
}
)
@@ -70,6 +72,7 @@ SELECT
name,
description,
state,
importance,
content_ref,
created_at,
updated_at,
@@ -115,6 +118,7 @@ INSERT INTO
framework_id,
category,
name,
importance,
state,
description,
content_ref,
@@ -128,6 +132,7 @@ VALUES (
@framework_id,
@category,
@name,
@importance,
@state,
@description,
@content_ref,
@@ -149,6 +154,7 @@ VALUES (
"created_at": c.CreatedAt,
"updated_at": c.UpdatedAt,
"state": c.State,
"importance": c.Importance,
}
_, err := conn.Exec(ctx, q, args)
return err
@@ -169,6 +175,7 @@ SELECT
name,
description,
state,
importance,
content_ref,
created_at,
updated_at,
@@ -213,6 +220,7 @@ UPDATE controls SET
description = COALESCE(@description, description),
category = COALESCE(@category, category),
state = COALESCE(@state, state),
importance = COALESCE(@importance, importance),
updated_at = @updated_at,
version = version + 1
WHERE %s
@@ -224,6 +232,7 @@ RETURNING
category,
name,
description,
importance,
state,
content_ref,
created_at,
@@ -235,23 +244,16 @@ RETURNING
args := pgx.NamedArgs{
"control_id": c.ID,
"expected_version": params.ExpectedVersion,
"name": params.Name,
"description": params.Description,
"category": params.Category,
"state": params.State,
"importance": params.Importance,
"updated_at": time.Now(),
}
maps.Copy(args, scope.SQLArguments())
if params.Name != nil {
args["name"] = *params.Name
}
if params.Description != nil {
args["description"] = *params.Description
}
if params.Category != nil {
args["category"] = *params.Category
}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query controls: %w", err)

View File

@@ -0,0 +1,100 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// 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"
"encoding/json"
"fmt"
)
type ControlImportance uint8
const (
ControlImportanceMandatory ControlImportance = iota
ControlImportancePreferred
ControlImportanceAdvanced
)
func (i ControlImportance) String() string {
return []string{"MANDATORY", "PREFERRED", "ADVANCED"}[i]
}
func (i *ControlImportance) Scan(value interface{}) error {
switch v := value.(type) {
case uint8:
*i = ControlImportance(v)
case string:
switch v {
case "MANDATORY":
*i = ControlImportanceMandatory
case "PREFERRED":
*i = ControlImportancePreferred
case "ADVANCED":
*i = ControlImportanceAdvanced
default:
return fmt.Errorf("invalid ControlImportance value: %q", v)
}
default:
return fmt.Errorf("unsupported type for ControlImportance: %T", value)
}
return nil
}
func (i ControlImportance) Value() (driver.Value, error) {
return i.String(), nil
}
func (i ControlImportance) MarshalJSON() ([]byte, error) {
return json.Marshal(i.String())
}
func (i *ControlImportance) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
switch s {
case "MANDATORY":
*i = ControlImportanceMandatory
case "PREFERRED":
*i = ControlImportancePreferred
case "ADVANCED":
*i = ControlImportanceAdvanced
default:
return fmt.Errorf("invalid ControlImportance value: %q", s)
}
return nil
}
func (i *ControlImportance) UnmarshalText(text []byte) error {
var s string
if err := json.Unmarshal(text, &s); err != nil {
return err
}
switch s {
case "MANDATORY":
*i = ControlImportanceMandatory
case "PREFERRED":
*i = ControlImportancePreferred
case "ADVANCED":
*i = ControlImportanceAdvanced
default:
return fmt.Errorf("invalid ControlImportance value: %q", s)
}
return nil
}

View File

@@ -0,0 +1,8 @@
CREATE TYPE control_importance AS ENUM ('MANDATORY', 'PREFERRED', 'ADVANCED');
ALTER TABLE controls ADD COLUMN importance control_importance NOT NULL DEFAULT 'MANDATORY';
ALTER TABLE controls ALTER COLUMN importance DROP DEFAULT;
ALTER TABLE tasks ADD COLUMN time_estimate INTERVAL NOT NULL DEFAULT '00:30:00';
ALTER TABLE tasks ALTER COLUMN time_estimate DROP DEFAULT;

View File

@@ -36,6 +36,7 @@ type (
Description string
ContentRef string
Category string
Importance coredata.ControlImportance
}
UpdateControlRequest struct {
@@ -45,6 +46,7 @@ type (
Description *string
Category *string
State *coredata.ControlState
Importance *coredata.ControlImportance
}
)
@@ -78,6 +80,7 @@ func (s ControlService) Update(
Description: req.Description,
Category: req.Category,
State: req.State,
Importance: req.Importance,
}
control := &coredata.Control{ID: req.ID}
@@ -139,6 +142,7 @@ func (s ControlService) Create(
Description: req.Description,
Category: req.Category,
State: coredata.ControlStateNotStarted,
Importance: req.Importance,
ContentRef: req.ContentRef,
CreatedAt: now,
UpdatedAt: now,

View File

@@ -70,6 +70,22 @@ enum PeopleKind
)
}
enum ControlImportance
@goModel(model: "github.com/getprobo/probo/pkg/coredata.ControlImportance") {
MANDATORY
@goEnum(
value: "github.com/getprobo/probo/pkg/coredata.ControlImportanceMandatory"
)
PREFERRED
@goEnum(
value: "github.com/getprobo/probo/pkg/coredata.ControlImportancePreferred"
)
ADVANCED
@goEnum(
value: "github.com/getprobo/probo/pkg/coredata.ControlImportanceAdvanced"
)
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
@@ -216,6 +232,7 @@ type Control implements Node {
name: String!
description: String!
state: ControlState!
importance: ControlImportance!
tasks(
first: Int
@@ -308,24 +325,35 @@ type Mutation {
createVendor(input: CreateVendorInput!): CreateVendorPayload!
updateVendor(input: UpdateVendorInput!): UpdateVendorPayload!
deleteVendor(input: DeleteVendorInput!): DeleteVendorPayload!
createPeople(input: CreatePeopleInput!): CreatePeoplePayload!
updatePeople(input: UpdatePeopleInput!): UpdatePeoplePayload!
deletePeople(input: DeletePeopleInput!): DeletePeoplePayload!
createOrganization(
input: CreateOrganizationInput!
): CreateOrganizationPayload!
updateOrganization(
input: UpdateOrganizationInput!
): UpdateOrganizationPayload!
deleteOrganization(
input: DeleteOrganizationInput!
): DeleteOrganizationPayload!
createTask(input: CreateTaskInput!): CreateTaskPayload!
updateTask(input: UpdateTaskInput!): UpdateTaskPayload!
deleteTask(input: DeleteTaskInput!): DeleteTaskPayload!
createFramework(input: CreateFrameworkInput!): CreateFrameworkPayload!
createControl(input: CreateControlInput!): CreateControlPayload!
updateFramework(input: UpdateFrameworkInput!): UpdateFrameworkPayload!
importFramework(input: ImportFrameworkInput!): ImportFrameworkPayload!
createControl(input: CreateControlInput!): CreateControlPayload!
updateControl(input: UpdateControlInput!): UpdateControlPayload!
uploadEvidence(input: UploadEvidenceInput!): UploadEvidencePayload!
deleteEvidence(input: DeleteEvidenceInput!): DeleteEvidencePayload!
createPolicy(input: CreatePolicyInput!): CreatePolicyPayload!
updatePolicy(input: UpdatePolicyInput!): UpdatePolicyPayload!
deletePolicy(input: DeletePolicyInput!): DeletePolicyPayload!
@@ -431,6 +459,12 @@ input CreateOrganizationInput {
name: String!
}
input UpdateOrganizationInput {
organizationId: ID!
name: String
logoUrl: String
}
input DeleteOrganizationInput {
organizationId: ID!
}
@@ -439,6 +473,10 @@ type CreateOrganizationPayload {
organizationEdge: OrganizationEdge!
}
type UpdateOrganizationPayload {
organization: Organization!
}
type DeleteOrganizationPayload {
deletedOrganizationId: ID!
}
@@ -483,6 +521,7 @@ input CreateControlInput {
name: String!
description: String!
category: String!
importance: ControlImportance!
}
type CreateControlPayload {
@@ -508,6 +547,7 @@ input UpdateControlInput {
description: String
category: String
state: ControlState
importance: ControlImportance
}
type UpdateControlPayload {
@@ -616,3 +656,12 @@ input ConfirmEmailInput {
type ConfirmEmailPayload {
success: Boolean!
}
input ImportFrameworkInput {
organizationId: ID!
file: Upload!
}
type ImportFrameworkPayload {
frameworkEdge: FrameworkEdge!
}

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -25,15 +25,16 @@ type ConfirmEmailPayload struct {
}
type Control struct {
ID gid.GID `json:"id"`
Version int `json:"version"`
Category string `json:"category"`
Name string `json:"name"`
Description string `json:"description"`
State coredata.ControlState `json:"state"`
Tasks *TaskConnection `json:"tasks"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
ID gid.GID `json:"id"`
Version int `json:"version"`
Category string `json:"category"`
Name string `json:"name"`
Description string `json:"description"`
State coredata.ControlState `json:"state"`
Importance coredata.ControlImportance `json:"importance"`
Tasks *TaskConnection `json:"tasks"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (Control) IsNode() {}
@@ -50,10 +51,11 @@ type ControlEdge struct {
}
type CreateControlInput struct {
FrameworkID gid.GID `json:"frameworkId"`
Name string `json:"name"`
Description string `json:"description"`
Category string `json:"category"`
FrameworkID gid.GID `json:"frameworkId"`
Name string `json:"name"`
Description string `json:"description"`
Category string `json:"category"`
Importance coredata.ControlImportance `json:"importance"`
}
type CreateControlPayload struct {
@@ -225,6 +227,15 @@ type FrameworkEdge struct {
Node *Framework `json:"node"`
}
type ImportFrameworkInput struct {
OrganizationID gid.GID `json:"organizationId"`
File graphql.Upload `json:"file"`
}
type ImportFrameworkPayload struct {
FrameworkEdge *FrameworkEdge `json:"frameworkEdge"`
}
type Mutation struct {
}
@@ -342,12 +353,13 @@ type TaskEdge struct {
}
type UpdateControlInput struct {
ID gid.GID `json:"id"`
ExpectedVersion int `json:"expectedVersion"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
Category *string `json:"category,omitempty"`
State *coredata.ControlState `json:"state,omitempty"`
ID gid.GID `json:"id"`
ExpectedVersion int `json:"expectedVersion"`
Name *string `json:"name,omitempty"`
Description *string `json:"description,omitempty"`
Category *string `json:"category,omitempty"`
State *coredata.ControlState `json:"state,omitempty"`
Importance *coredata.ControlImportance `json:"importance,omitempty"`
}
type UpdateControlPayload struct {
@@ -365,6 +377,16 @@ type UpdateFrameworkPayload struct {
Framework *Framework `json:"framework"`
}
type UpdateOrganizationInput struct {
OrganizationID gid.GID `json:"organizationId"`
Name *string `json:"name,omitempty"`
LogoURL *string `json:"logoUrl,omitempty"`
}
type UpdateOrganizationPayload struct {
Organization *Organization `json:"organization"`
}
type UpdatePeopleInput struct {
ID gid.GID `json:"id"`
ExpectedVersion int `json:"expectedVersion"`

View File

@@ -321,6 +321,7 @@ func (r *mutationResolver) UpdateControl(ctx context.Context, input types.Update
Description: input.Description,
Category: input.Category,
State: input.State,
Importance: input.Importance,
})
if err != nil {
return nil, fmt.Errorf("cannot update control: %w", err)