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

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