Add tasks page

Signed-off-by: Bryan Frimin <bryan@getprobo.com>
This commit is contained in:
Bryan Frimin
2025-05-05 22:34:04 -07:00
parent c93e795da5
commit 046c42eb48
22 changed files with 3929 additions and 197 deletions

View File

@@ -30,7 +30,6 @@ import (
type (
Measure struct {
ID gid.GID `db:"id"`
TenantID gid.TenantID `db:"tenant_id"`
OrganizationID gid.GID `db:"organization_id"`
Category string `db:"category"`
Name string `db:"name"`
@@ -82,7 +81,6 @@ WITH msrs AS (
)
SELECT
id,
tenant_id,
organization_id,
category,
name,
@@ -146,7 +144,6 @@ WITH mtgtns AS (
)
SELECT
id,
tenant_id,
organization_id,
category,
name,
@@ -191,7 +188,6 @@ func (m *Measures) LoadByOrganizationID(
q := `
SELECT
id,
tenant_id,
organization_id,
category,
name,
@@ -237,7 +233,6 @@ func (m *Measure) LoadByID(
q := `
SELECT
id,
tenant_id,
organization_id,
category,
name,
@@ -311,7 +306,6 @@ ON CONFLICT (organization_id, reference_id) DO UPDATE SET
category = @category,
updated_at = @updated_at
RETURNING
tenant_id,
id,
organization_id,
category,

View File

@@ -0,0 +1,8 @@
ALTER TABLE tasks ADD COLUMN organization_id TEXT REFERENCES organizations(id) ON DELETE CASCADE;
UPDATE tasks
SET organization_id = m.organization_id::text
FROM measures m
WHERE tasks.measure_id = m.id;
ALTER TABLE tasks ALTER COLUMN organization_id SET NOT NULL;

View File

@@ -166,8 +166,9 @@ WHERE %s
`
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
args := pgx.NamedArgs{"organization_id": organizationID}
args := pgx.StrictNamedArgs{"organization_id": organizationID}
maps.Copy(args, scope.SQLArguments())
maps.Copy(args, cursor.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {

View File

@@ -29,16 +29,17 @@ import (
type (
Task struct {
ID gid.GID `db:"id"`
MeasureID gid.GID `db:"measure_id"`
Name string `db:"name"`
Description string `db:"description"`
State TaskState `db:"state"`
ReferenceID string `db:"reference_id"`
TimeEstimate *time.Duration `db:"time_estimate"`
AssignedToID *gid.GID `db:"assigned_to"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
ID gid.GID `db:"id"`
OrganizationID gid.GID `db:"organization_id"`
MeasureID *gid.GID `db:"measure_id"`
Name string `db:"name"`
Description string `db:"description"`
State TaskState `db:"state"`
ReferenceID string `db:"reference_id"`
TimeEstimate *time.Duration `db:"time_estimate"`
AssignedToID *gid.GID `db:"assigned_to"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
Tasks []*Task
@@ -62,6 +63,7 @@ func (c *Task) LoadByID(
q := `
SELECT
id,
organization_id,
measure_id,
name,
description,
@@ -109,6 +111,7 @@ INSERT INTO
tasks (
tenant_id,
id,
organization_id,
measure_id,
name,
description,
@@ -122,6 +125,7 @@ INSERT INTO
VALUES (
@tenant_id,
@task_id,
@organization_id,
@measure_id,
@name,
@description,
@@ -135,17 +139,18 @@ VALUES (
`
args := pgx.StrictNamedArgs{
"tenant_id": scope.GetTenantID(),
"task_id": c.ID,
"measure_id": c.MeasureID,
"name": c.Name,
"description": c.Description,
"reference_id": c.ReferenceID,
"state": c.State,
"time_estimate": c.TimeEstimate,
"assigned_to": c.AssignedToID,
"created_at": c.CreatedAt,
"updated_at": c.UpdatedAt,
"tenant_id": scope.GetTenantID(),
"task_id": c.ID,
"organization_id": c.OrganizationID,
"measure_id": c.MeasureID,
"name": c.Name,
"description": c.Description,
"reference_id": c.ReferenceID,
"state": c.State,
"time_estimate": c.TimeEstimate,
"assigned_to": c.AssignedToID,
"created_at": c.CreatedAt,
"updated_at": c.UpdatedAt,
}
_, err := conn.Exec(ctx, q, args)
return err
@@ -161,6 +166,7 @@ INSERT INTO
tasks (
tenant_id,
id,
organization_id,
measure_id,
name,
description,
@@ -174,6 +180,7 @@ INSERT INTO
VALUES (
@tenant_id,
@task_id,
@organization_id,
@measure_id,
@name,
@description,
@@ -190,6 +197,7 @@ ON CONFLICT (measure_id, reference_id) DO UPDATE SET
updated_at = @updated_at
RETURNING
id,
organization_id,
measure_id,
name,
description,
@@ -202,17 +210,18 @@ RETURNING
`
args := pgx.StrictNamedArgs{
"tenant_id": scope.GetTenantID(),
"task_id": c.ID,
"measure_id": c.MeasureID,
"name": c.Name,
"description": c.Description,
"reference_id": c.ReferenceID,
"state": c.State,
"time_estimate": c.TimeEstimate,
"assigned_to": c.AssignedToID,
"created_at": c.CreatedAt,
"updated_at": c.UpdatedAt,
"tenant_id": scope.GetTenantID(),
"task_id": c.ID,
"organization_id": c.OrganizationID,
"measure_id": c.MeasureID,
"name": c.Name,
"description": c.Description,
"reference_id": c.ReferenceID,
"state": c.State,
"time_estimate": c.TimeEstimate,
"assigned_to": c.AssignedToID,
"created_at": c.CreatedAt,
"updated_at": c.UpdatedAt,
}
rows, err := conn.Query(ctx, q, args)
if err != nil {
@@ -229,6 +238,54 @@ RETURNING
return nil
}
func (c *Tasks) LoadByOrganizationID(
ctx context.Context,
conn pg.Conn,
scope Scoper,
organizationID gid.GID,
cursor *page.Cursor[TaskOrderField],
) error {
q := `
SELECT
id,
measure_id,
organization_id,
name,
description,
state,
reference_id,
time_estimate,
assigned_to,
created_at,
updated_at
FROM
tasks
WHERE
%s
AND organization_id = @organization_id
AND %s
`
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
args := pgx.StrictNamedArgs{"organization_id": organizationID}
maps.Copy(args, scope.SQLArguments())
maps.Copy(args, cursor.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query tasks: %w", err)
}
tasks, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Task])
if err != nil {
return fmt.Errorf("cannot collect tasks: %w", err)
}
*c = tasks
return nil
}
func (c *Tasks) LoadByMeasureID(
ctx context.Context,
conn pg.Conn,
@@ -240,6 +297,7 @@ func (c *Tasks) LoadByMeasureID(
SELECT
id,
measure_id,
organization_id,
name,
description,
state,
@@ -288,7 +346,8 @@ SET
description = @description,
state = @state,
time_estimate = @time_estimate,
updated_at = @updated_at
updated_at = @updated_at,
assigned_to = @assigned_to
WHERE %s
AND id = @task_id
`
@@ -301,6 +360,7 @@ WHERE %s
"state": c.State,
"time_estimate": c.TimeEstimate,
"updated_at": c.UpdatedAt,
"assigned_to": c.AssignedToID,
}
maps.Copy(args, scope.SQLArguments())
@@ -309,102 +369,6 @@ WHERE %s
return err
}
func (c *Task) AssignTo(
ctx context.Context,
conn pg.Conn,
scope Scoper,
assignTo gid.GID,
) error {
q := `
UPDATE tasks SET
assigned_to = @assigned_to,
updated_at = @updated_at
WHERE %s
AND id = @task_id
RETURNING
id,
measure_id,
name,
description,
reference_id,
state,
time_estimate,
assigned_to,
created_at,
updated_at
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.NamedArgs{
"task_id": c.ID,
"assigned_to": assignTo,
"updated_at": time.Now(),
}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query tasks: %w", err)
}
task, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Task])
if err != nil {
return fmt.Errorf("cannot collect tasks: %w", err)
}
*c = task
return nil
}
func (c *Task) Unassign(
ctx context.Context,
conn pg.Conn,
scope Scoper,
) error {
q := `
UPDATE tasks SET
assigned_to = NULL,
updated_at = @updated_at
WHERE %s
AND id = @task_id
RETURNING
id,
measure_id,
name,
description,
reference_id,
state,
time_estimate,
assigned_to,
created_at,
updated_at
`
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.NamedArgs{
"task_id": c.ID,
"updated_at": time.Now(),
}
maps.Copy(args, scope.SQLArguments())
rows, err := conn.Query(ctx, q, args)
if err != nil {
return fmt.Errorf("cannot query tasks: %w", err)
}
task, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[Task])
if err != nil {
return fmt.Errorf("cannot collect tasks: %w", err)
}
*c = task
return nil
}
func (c *Task) Delete(
ctx context.Context,
conn pg.Conn,

View File

@@ -119,8 +119,12 @@ func (s EvidenceService) Request(
return fmt.Errorf("cannot load task: %w", err)
}
if task.MeasureID == nil {
return fmt.Errorf("task %q has no measure", req.TaskID)
}
evidence.TaskID = req.TaskID
evidence.MeasureID = task.MeasureID
evidence.MeasureID = *task.MeasureID
} else if req.MeasureID != nil {
evidence.MeasureID = *req.MeasureID
} else {
@@ -298,7 +302,11 @@ func (s EvidenceService) UploadTaskEvidence(
return fmt.Errorf("cannot load task %q: %w", req.TaskID, err)
}
evidence.MeasureID = task.MeasureID
if task.MeasureID == nil {
return fmt.Errorf("task %q has no measure", req.TaskID)
}
evidence.MeasureID = *task.MeasureID
if err := evidence.Insert(ctx, conn, s.svc.scope); err != nil {
return fmt.Errorf("cannot insert evidence: %w", err)

View File

@@ -168,14 +168,15 @@ func (s MeasureService) Import(
taskID := gid.New(organizationID.TenantID(), coredata.TaskEntityType)
task := &coredata.Task{
ID: taskID,
MeasureID: measure.ID,
Name: req.Measures[i].Tasks[j].Name,
Description: req.Measures[i].Tasks[j].Description,
ReferenceID: req.Measures[i].Tasks[j].ReferenceID,
State: coredata.TaskStateTodo,
CreatedAt: now,
UpdatedAt: now,
ID: taskID,
OrganizationID: organizationID,
MeasureID: &measure.ID,
Name: req.Measures[i].Tasks[j].Name,
Description: req.Measures[i].Tasks[j].Description,
ReferenceID: req.Measures[i].Tasks[j].ReferenceID,
State: coredata.TaskStateTodo,
CreatedAt: now,
UpdatedAt: now,
}
if err := task.Upsert(ctx, tx, s.svc.scope); err != nil {

View File

@@ -32,11 +32,12 @@ type (
}
CreateTaskRequest struct {
MeasureID gid.GID
Name string
Description string
TimeEstimate *time.Duration
AssignedToID *gid.GID
OrganizationID gid.GID
MeasureID *gid.GID
Name string
Description string
TimeEstimate *time.Duration
AssignedToID *gid.GID
}
UpdateTaskRequest struct {
@@ -61,21 +62,23 @@ func (s TaskService) Create(
}
task := &coredata.Task{
ID: taskID,
MeasureID: req.MeasureID,
Name: req.Name,
Description: req.Description,
TimeEstimate: req.TimeEstimate,
AssignedToID: req.AssignedToID,
State: coredata.TaskStateTodo,
ReferenceID: "custom-task-" + referenceID.String(),
CreatedAt: now,
UpdatedAt: now,
ID: taskID,
OrganizationID: req.OrganizationID,
MeasureID: req.MeasureID,
Name: req.Name,
Description: req.Description,
TimeEstimate: req.TimeEstimate,
AssignedToID: req.AssignedToID,
State: coredata.TaskStateTodo,
ReferenceID: "custom-task-" + referenceID.String(),
CreatedAt: now,
UpdatedAt: now,
}
err = s.svc.pg.WithTx(
ctx,
func(conn pg.Conn) error {
if err := task.Insert(ctx, conn, s.svc.scope); err != nil {
return fmt.Errorf("cannot insert task: %w", err)
}
@@ -119,7 +122,18 @@ func (s TaskService) Assign(
err := s.svc.pg.WithTx(
ctx,
func(conn pg.Conn) error {
return task.AssignTo(ctx, conn, s.svc.scope, assignedToID)
if err := task.LoadByID(ctx, conn, s.svc.scope, taskID); err != nil {
return fmt.Errorf("cannot load task %q: %w", taskID, err)
}
task.AssignedToID = &assignedToID
task.UpdatedAt = time.Now()
if err := task.Update(ctx, conn, s.svc.scope); err != nil {
return fmt.Errorf("cannot assign task %q to %q: %w", taskID, assignedToID, err)
}
return nil
},
)
if err != nil {
@@ -133,12 +147,23 @@ func (s TaskService) Unassign(
ctx context.Context,
taskID gid.GID,
) (*coredata.Task, error) {
task := &coredata.Task{ID: taskID}
task := &coredata.Task{}
err := s.svc.pg.WithTx(
ctx,
func(conn pg.Conn) error {
return task.Unassign(ctx, conn, s.svc.scope)
if err := task.LoadByID(ctx, conn, s.svc.scope, taskID); err != nil {
return fmt.Errorf("cannot load task %q: %w", taskID, err)
}
task.AssignedToID = nil
task.UpdatedAt = time.Now()
if err := task.Update(ctx, conn, s.svc.scope); err != nil {
return fmt.Errorf("cannot unassign task %q: %w", taskID, err)
}
return nil
},
)
if err != nil {
@@ -152,7 +177,8 @@ func (s TaskService) Update(
ctx context.Context,
req UpdateTaskRequest,
) (*coredata.Task, error) {
task := &coredata.Task{ID: req.TaskID}
task := &coredata.Task{}
err := s.svc.pg.WithTx(
ctx,
@@ -212,6 +238,26 @@ func (s TaskService) Delete(
return nil
}
func (s TaskService) ListForOrganizationID(
ctx context.Context,
organizationID gid.GID,
cursor *page.Cursor[coredata.TaskOrderField],
) (*page.Page[*coredata.Task, coredata.TaskOrderField], error) {
var tasks coredata.Tasks
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
return tasks.LoadByOrganizationID(ctx, conn, s.svc.scope, organizationID, cursor)
},
)
if err != nil {
return nil, err
}
return page.NewPage(tasks, cursor), nil
}
func (s TaskService) ListForMeasureID(
ctx context.Context,
measureID gid.GID,

View File

@@ -461,6 +461,14 @@ type Organization implements Node {
orderBy: RiskOrder
): RiskConnection! @goField(forceResolver: true)
tasks(
first: Int
after: CursorKey
last: Int
before: CursorKey
orderBy: TaskOrder
): TaskConnection! @goField(forceResolver: true)
createdAt: Datetime!
updatedAt: Datetime!
}
@@ -498,6 +506,8 @@ type Vendor implements Node {
name: String!
description: String
organization: Organization! @goField(forceResolver: true)
complianceReports(
first: Int
after: CursorKey
@@ -538,10 +548,8 @@ type VendorComplianceReport implements Node {
reportDate: Datetime!
validUntil: Datetime
reportName: String!
fileUrl: String! @goField(forceResolver: true)
fileSize: Int!
createdAt: Datetime!
updatedAt: Datetime!
}
@@ -551,6 +559,8 @@ type Framework implements Node {
name: String!
description: String!
organization: Organization! @goField(forceResolver: true)
controls(
first: Int
after: CursorKey
@@ -569,6 +579,8 @@ type Control implements Node {
name: String!
description: String!
framework: Framework! @goField(forceResolver: true)
measures(
first: Int
after: CursorKey
@@ -640,6 +652,9 @@ type Task implements Node {
timeEstimate: Duration
assignedTo: People @goField(forceResolver: true)
organization: Organization! @goField(forceResolver: true)
measure: Measure @goField(forceResolver: true)
evidences(
first: Int
after: CursorKey
@@ -663,6 +678,9 @@ type Evidence implements Node {
url: String
description: String!
task: Task @goField(forceResolver: true)
measure: Measure! @goField(forceResolver: true)
createdAt: Datetime!
updatedAt: Datetime!
}
@@ -673,6 +691,7 @@ type Policy implements Node {
description: String!
currentPublishedVersion: Int
owner: People! @goField(forceResolver: true)
organization: Organization! @goField(forceResolver: true)
versions(
first: Int
@@ -710,6 +729,7 @@ type Risk implements Node {
note: String!
owner: People @goField(forceResolver: true)
organization: Organization! @goField(forceResolver: true)
measures(
first: Int
@@ -1140,7 +1160,8 @@ input ImportMeasureInput {
}
input CreateTaskInput {
measureId: ID!
organizationId: ID!
measureId: ID
name: String!
description: String!
timeEstimate: Duration

File diff suppressed because it is too large Load Diff

View File

@@ -67,6 +67,7 @@ type Control struct {
ReferenceID string `json:"referenceId"`
Name string `json:"name"`
Description string `json:"description"`
Framework *Framework `json:"framework"`
Measures *MeasureConnection `json:"measures"`
Policies *PolicyConnection `json:"policies"`
CreatedAt time.Time `json:"createdAt"`
@@ -215,11 +216,12 @@ type CreateRiskPolicyMappingPayload struct {
}
type CreateTaskInput struct {
MeasureID gid.GID `json:"measureId"`
Name string `json:"name"`
Description string `json:"description"`
TimeEstimate *time.Duration `json:"timeEstimate,omitempty"`
AssignedToID *gid.GID `json:"assignedToId,omitempty"`
OrganizationID gid.GID `json:"organizationId"`
MeasureID *gid.GID `json:"measureId,omitempty"`
Name string `json:"name"`
Description string `json:"description"`
TimeEstimate *time.Duration `json:"timeEstimate,omitempty"`
AssignedToID *gid.GID `json:"assignedToId,omitempty"`
}
type CreateTaskPayload struct {
@@ -389,6 +391,8 @@ type Evidence struct {
Filename string `json:"filename"`
URL *string `json:"url,omitempty"`
Description string `json:"description"`
Task *Task `json:"task,omitempty"`
Measure *Measure `json:"measure"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
@@ -407,12 +411,13 @@ type EvidenceEdge struct {
}
type Framework struct {
ID gid.GID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Controls *ControlConnection `json:"controls"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
ID gid.GID `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Organization *Organization `json:"organization"`
Controls *ControlConnection `json:"controls"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
func (Framework) IsNode() {}
@@ -509,6 +514,7 @@ type Organization struct {
Policies *PolicyConnection `json:"policies"`
Measures *MeasureConnection `json:"measures"`
Risks *RiskConnection `json:"risks"`
Tasks *TaskConnection `json:"tasks"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
@@ -567,6 +573,7 @@ type Policy struct {
Description string `json:"description"`
CurrentPublishedVersion *int `json:"currentPublishedVersion,omitempty"`
Owner *People `json:"owner"`
Organization *Organization `json:"organization"`
Versions *PolicyVersionConnection `json:"versions"`
Controls *ControlConnection `json:"controls"`
CreatedAt time.Time `json:"createdAt"`
@@ -702,6 +709,7 @@ type Risk struct {
ResidualSeverity int `json:"residualSeverity"`
Note string `json:"note"`
Owner *People `json:"owner,omitempty"`
Organization *Organization `json:"organization"`
Measures *MeasureConnection `json:"measures"`
Policies *PolicyConnection `json:"policies"`
Controls *ControlConnection `json:"controls"`
@@ -742,6 +750,8 @@ type Task struct {
State coredata.TaskState `json:"state"`
TimeEstimate *time.Duration `json:"timeEstimate,omitempty"`
AssignedTo *People `json:"assignedTo,omitempty"`
Organization *Organization `json:"organization"`
Measure *Measure `json:"measure,omitempty"`
Evidences *EvidenceConnection `json:"evidences"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
@@ -943,6 +953,7 @@ type Vendor struct {
ID gid.GID `json:"id"`
Name string `json:"name"`
Description *string `json:"description,omitempty"`
Organization *Organization `json:"organization"`
ComplianceReports *VendorComplianceReportConnection `json:"complianceReports"`
RiskAssessments *VendorRiskAssessmentConnection `json:"riskAssessments"`
BusinessOwner *People `json:"businessOwner,omitempty"`

View File

@@ -20,6 +20,23 @@ import (
"github.com/vektah/gqlparser/v2/gqlerror"
)
// Framework is the resolver for the framework field.
func (r *controlResolver) Framework(ctx context.Context, obj *types.Control) (*types.Framework, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
control, err := svc.Controls.Get(ctx, obj.ID)
if err != nil {
panic(fmt.Errorf("cannot get control: %w", err))
}
framework, err := svc.Frameworks.Get(ctx, control.FrameworkID)
if err != nil {
panic(fmt.Errorf("cannot get framework: %w", err))
}
return types.NewFramework(framework), nil
}
// Measures is the resolver for the measures field.
func (r *controlResolver) Measures(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy) (*types.MeasureConnection, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
@@ -87,6 +104,61 @@ func (r *evidenceResolver) FileURL(ctx context.Context, obj *types.Evidence) (*s
return &result, nil
}
// Task is the resolver for the task field.
func (r *evidenceResolver) Task(ctx context.Context, obj *types.Evidence) (*types.Task, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
evidence, err := svc.Evidences.Get(ctx, obj.ID)
if err != nil {
return nil, fmt.Errorf("cannot load evidence: %w", err)
}
if evidence.TaskID == nil {
return nil, fmt.Errorf("evidence is not associated with a task")
}
task, err := svc.Tasks.Get(ctx, *evidence.TaskID)
if err != nil {
return nil, fmt.Errorf("cannot load task: %w", err)
}
return types.NewTask(task), nil
}
// Measure is the resolver for the measure field.
func (r *evidenceResolver) Measure(ctx context.Context, obj *types.Evidence) (*types.Measure, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
evidence, err := svc.Evidences.Get(ctx, obj.ID)
if err != nil {
return nil, fmt.Errorf("cannot load evidence: %w", err)
}
measure, err := svc.Measures.Get(ctx, evidence.MeasureID)
if err != nil {
return nil, fmt.Errorf("cannot load measure: %w", err)
}
return types.NewMeasure(measure), nil
}
// Organization is the resolver for the organization field.
func (r *frameworkResolver) Organization(ctx context.Context, obj *types.Framework) (*types.Organization, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
framework, err := svc.Frameworks.Get(ctx, obj.ID)
if err != nil {
return nil, fmt.Errorf("cannot load framework: %w", err)
}
organization, err := svc.Organizations.Get(ctx, framework.OrganizationID)
if err != nil {
return nil, fmt.Errorf("cannot load organization: %w", err)
}
return types.NewOrganization(organization), nil
}
// Controls is the resolver for the controls field.
func (r *frameworkResolver) Controls(ctx context.Context, obj *types.Framework, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy) (*types.ControlConnection, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
@@ -689,10 +761,11 @@ func (r *mutationResolver) CreateTask(ctx context.Context, input types.CreateTas
svc := GetTenantService(ctx, r.proboSvc, input.MeasureID.TenantID())
task, err := svc.Tasks.Create(ctx, probo.CreateTaskRequest{
MeasureID: input.MeasureID,
Name: input.Name,
Description: input.Description,
TimeEstimate: input.TimeEstimate,
MeasureID: input.MeasureID,
OrganizationID: input.OrganizationID,
Name: input.Name,
Description: input.Description,
TimeEstimate: input.TimeEstimate,
})
if err != nil {
panic(fmt.Errorf("cannot create task: %w", err))
@@ -1416,6 +1489,31 @@ func (r *organizationResolver) Risks(ctx context.Context, obj *types.Organizatio
return types.NewRiskConnection(page), nil
}
// Tasks is the resolver for the tasks field.
func (r *organizationResolver) Tasks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TaskOrderBy) (*types.TaskConnection, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
pageOrderBy := page.OrderBy[coredata.TaskOrderField]{
Field: coredata.TaskOrderFieldCreatedAt,
Direction: page.OrderDirectionDesc,
}
if orderBy != nil {
pageOrderBy = page.OrderBy[coredata.TaskOrderField]{
Field: orderBy.Field,
Direction: orderBy.Direction,
}
}
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
page, err := svc.Tasks.ListForOrganizationID(ctx, obj.ID, cursor)
if err != nil {
panic(fmt.Errorf("cannot list organization tasks: %w", err))
}
return types.NewTaskConnection(page), nil
}
// Owner is the resolver for the owner field.
func (r *policyResolver) Owner(ctx context.Context, obj *types.Policy) (*types.People, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
@@ -1434,6 +1532,23 @@ func (r *policyResolver) Owner(ctx context.Context, obj *types.Policy) (*types.P
return types.NewPeople(owner), nil
}
// Organization is the resolver for the organization field.
func (r *policyResolver) Organization(ctx context.Context, obj *types.Policy) (*types.Organization, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
policy, err := svc.Policies.Get(ctx, obj.ID)
if err != nil {
panic(fmt.Errorf("cannot get policy: %w", err))
}
organization, err := svc.Organizations.Get(ctx, policy.OrganizationID)
if err != nil {
panic(fmt.Errorf("cannot get organization: %w", err))
}
return types.NewOrganization(organization), nil
}
// Versions is the resolver for the versions field.
func (r *policyResolver) Versions(ctx context.Context, obj *types.Policy, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.PolicyVersionOrderBy, filter *types.PolicyVersionFilter) (*types.PolicyVersionConnection, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
@@ -1727,6 +1842,23 @@ func (r *riskResolver) Owner(ctx context.Context, obj *types.Risk) (*types.Peopl
return types.NewPeople(owner), nil
}
// Organization is the resolver for the organization field.
func (r *riskResolver) Organization(ctx context.Context, obj *types.Risk) (*types.Organization, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
risk, err := svc.Risks.Get(ctx, obj.ID)
if err != nil {
panic(fmt.Errorf("cannot get risk: %w", err))
}
organization, err := svc.Organizations.Get(ctx, risk.OrganizationID)
if err != nil {
panic(fmt.Errorf("cannot get organization: %w", err))
}
return types.NewOrganization(organization), nil
}
// Measures is the resolver for the measures field.
func (r *riskResolver) Measures(ctx context.Context, obj *types.Risk, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy) (*types.MeasureConnection, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
@@ -1823,6 +1955,40 @@ func (r *taskResolver) AssignedTo(ctx context.Context, obj *types.Task) (*types.
return types.NewPeople(people), nil
}
// Organization is the resolver for the organization field.
func (r *taskResolver) Organization(ctx context.Context, obj *types.Task) (*types.Organization, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
task, err := svc.Tasks.Get(ctx, obj.ID)
if err != nil {
panic(fmt.Errorf("cannot get task: %w", err))
}
organization, err := svc.Organizations.Get(ctx, task.OrganizationID)
if err != nil {
panic(fmt.Errorf("cannot get organization: %w", err))
}
return types.NewOrganization(organization), nil
}
// Measure is the resolver for the measure field.
func (r *taskResolver) Measure(ctx context.Context, obj *types.Task) (*types.Measure, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
task, err := svc.Tasks.Get(ctx, obj.ID)
if err != nil {
panic(fmt.Errorf("cannot get task: %w", err))
}
measure, err := svc.Measures.Get(ctx, *task.MeasureID)
if err != nil {
panic(fmt.Errorf("cannot get measure: %w", err))
}
return types.NewMeasure(measure), nil
}
// Evidences is the resolver for the evidences field.
func (r *taskResolver) Evidences(ctx context.Context, obj *types.Task, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.EvidenceOrderBy) (*types.EvidenceConnection, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
@@ -1859,6 +2025,23 @@ func (r *userResolver) People(ctx context.Context, obj *types.User, organization
return types.NewPeople(people), nil
}
// Organization is the resolver for the organization field.
func (r *vendorResolver) Organization(ctx context.Context, obj *types.Vendor) (*types.Organization, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
vendor, err := svc.Vendors.Get(ctx, obj.ID)
if err != nil {
panic(fmt.Errorf("cannot get vendor: %w", err))
}
organization, err := svc.Organizations.Get(ctx, vendor.OrganizationID)
if err != nil {
panic(fmt.Errorf("cannot get organization: %w", err))
}
return types.NewOrganization(organization), nil
}
// ComplianceReports is the resolver for the complianceReports field.
func (r *vendorResolver) ComplianceReports(ctx context.Context, obj *types.Vendor, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorComplianceReportOrderBy) (*types.VendorComplianceReportConnection, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())