diff --git a/pkg/server/api/console/v1/schema.graphql b/pkg/server/api/console/v1/schema.graphql index 858797c10..cf5d33f87 100644 --- a/pkg/server/api/console/v1/schema.graphql +++ b/pkg/server/api/console/v1/schema.graphql @@ -163,19 +163,63 @@ enum TrustCenterDocumentAccessStatus ) } -enum NonconformityStatus - @goModel(model: "go.probo.inc/probo/pkg/coredata.NonconformityStatus") { +enum FindingKind + @goModel(model: "go.probo.inc/probo/pkg/coredata.FindingKind") { + NONCONFORMITY + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingKindNonconformity" + ) + OBSERVATION + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingKindObservation" + ) + EXCEPTION + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingKindException" + ) +} + +enum FindingStatus + @goModel(model: "go.probo.inc/probo/pkg/coredata.FindingStatus") { OPEN @goEnum( - value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusOpen" + value: "go.probo.inc/probo/pkg/coredata.FindingStatusOpen" ) IN_PROGRESS @goEnum( - value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusInProgress" + value: "go.probo.inc/probo/pkg/coredata.FindingStatusInProgress" ) CLOSED @goEnum( - value: "go.probo.inc/probo/pkg/coredata.NonconformityStatusClosed" + value: "go.probo.inc/probo/pkg/coredata.FindingStatusClosed" + ) + RISK_ACCEPTED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingStatusRiskAccepted" + ) + MITIGATED + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingStatusMitigated" + ) + FALSE_POSITIVE + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingStatusFalsePositive" + ) +} + +enum FindingPriority + @goModel(model: "go.probo.inc/probo/pkg/coredata.FindingPriority") { + LOW + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingPriorityLow" + ) + MEDIUM + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingPriorityMedium" + ) + HIGH + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingPriorityHigh" ) } @@ -204,42 +248,6 @@ enum ObligationType ) } -enum ContinualImprovementStatus - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ContinualImprovementStatus" - ) { - OPEN - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ContinualImprovementStatusOpen" - ) - IN_PROGRESS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ContinualImprovementStatusInProgress" - ) - CLOSED - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ContinualImprovementStatusClosed" - ) -} - -enum ContinualImprovementPriority - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ContinualImprovementPriority" - ) { - LOW - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ContinualImprovementPriorityLow" - ) - MEDIUM - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ContinualImprovementPriorityMedium" - ) - HIGH - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ContinualImprovementPriorityHigh" - ) -} - enum RightsRequestType @goModel(model: "go.probo.inc/probo/pkg/coredata.RightsRequestType") { ACCESS @@ -1050,27 +1058,35 @@ enum AuditOrderField STATE @goEnum(value: "go.probo.inc/probo/pkg/coredata.AuditOrderFieldState") } -enum NonconformityOrderField - @goModel(model: "go.probo.inc/probo/pkg/coredata.NonconformityOrderField") { +enum FindingOrderField + @goModel(model: "go.probo.inc/probo/pkg/coredata.FindingOrderField") { CREATED_AT @goEnum( - value: "go.probo.inc/probo/pkg/coredata.NonconformityOrderFieldCreatedAt" + value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldCreatedAt" ) REFERENCE_ID @goEnum( - value: "go.probo.inc/probo/pkg/coredata.NonconformityOrderFieldReferenceId" + value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldReferenceId" ) - DATE_IDENTIFIED + IDENTIFIED_ON @goEnum( - value: "go.probo.inc/probo/pkg/coredata.NonconformityOrderFieldDateIdentified" + value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldIdentifiedOn" ) DUE_DATE @goEnum( - value: "go.probo.inc/probo/pkg/coredata.NonconformityOrderFieldDueDate" + value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldDueDate" ) STATUS @goEnum( - value: "go.probo.inc/probo/pkg/coredata.NonconformityOrderFieldStatus" + value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldStatus" + ) + PRIORITY + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldPriority" + ) + KIND + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.FindingOrderFieldKind" ) } @@ -1094,32 +1110,6 @@ enum ObligationOrderField ) } -enum ContinualImprovementOrderField - @goModel( - model: "go.probo.inc/probo/pkg/coredata.ContinualImprovementOrderField" - ) { - CREATED_AT - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ContinualImprovementOrderFieldCreatedAt" - ) - REFERENCE_ID - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ContinualImprovementOrderFieldReferenceId" - ) - TARGET_DATE - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ContinualImprovementOrderFieldTargetDate" - ) - STATUS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ContinualImprovementOrderFieldStatus" - ) - PRIORITY - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.ContinualImprovementOrderFieldPriority" - ) -} - enum RightsRequestOrderField @goModel(model: "go.probo.inc/probo/pkg/coredata.RightsRequestOrderField") { CREATED_AT @@ -1297,18 +1287,14 @@ enum SnapshotsType @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeVendors") ASSETS @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeAssets") DATA @goEnum(value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeData") - NONCONFORMITIES + FINDINGS @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeNonconformities" + value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeFindings" ) OBLIGATIONS @goEnum( value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeObligations" ) - CONTINUAL_IMPROVEMENTS - @goEnum( - value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeContinualImprovements" - ) PROCESSING_ACTIVITIES @goEnum( value: "go.probo.inc/probo/pkg/coredata.SnapshotsTypeProcessingActivities" @@ -1420,12 +1406,9 @@ input AuditOrder field: AuditOrderField! } -input NonconformityOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.NonconformityOrderBy" - ) { +input FindingOrder { + field: FindingOrderField! direction: OrderDirection! - field: NonconformityOrderField! } input ObligationOrder @@ -1436,13 +1419,6 @@ input ObligationOrder field: ObligationOrderField! } -input ContinualImprovementOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ContinualImprovementOrderBy" - ) { - direction: OrderDirection! - field: ContinualImprovementOrderField! -} input RightsRequestOrder @goModel( @@ -1615,17 +1591,18 @@ input StateOfApplicabilityFilter { snapshotId: ID } -input NonconformityFilter { +input FindingFilter { snapshotId: ID + kind: FindingKind + status: FindingStatus + priority: FindingPriority + ownerId: ID } input ObligationFilter { snapshotId: ID } -input ContinualImprovementFilter { - snapshotId: ID -} input ProcessingActivityFilter { snapshotId: ID @@ -1932,14 +1909,14 @@ type Organization implements Node { orderBy: AuditOrder ): AuditConnection! @goField(forceResolver: true) - nonconformities( + findings( first: Int after: CursorKey last: Int before: CursorKey - orderBy: NonconformityOrder - filter: NonconformityFilter = { snapshotId: null } - ): NonconformityConnection! @goField(forceResolver: true) + orderBy: FindingOrder + filter: FindingFilter = { snapshotId: null } + ): FindingConnection @goField(forceResolver: true) obligations( first: Int @@ -1950,15 +1927,6 @@ type Organization implements Node { filter: ObligationFilter = { snapshotId: null } ): ObligationConnection! @goField(forceResolver: true) - continualImprovements( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: ContinualImprovementOrder - filter: ContinualImprovementFilter = { snapshotId: null } - ): ContinualImprovementConnection! @goField(forceResolver: true) - rightsRequests( first: Int after: CursorKey @@ -2683,8 +2651,8 @@ type Risk implements Node { type Audit implements Node { id: ID! name: String - organization: Organization! @goField(forceResolver: true) - framework: Framework! @goField(forceResolver: true) + organization: Organization @goField(forceResolver: true) + framework: Framework @goField(forceResolver: true) validFrom: Datetime validUntil: Datetime report: Report @goField(forceResolver: true) @@ -2698,7 +2666,16 @@ type Audit implements Node { before: CursorKey orderBy: ControlOrder filter: ControlFilter - ): ControlConnection! @goField(forceResolver: true) + ): ControlConnection @goField(forceResolver: true) + + findings( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: FindingOrder + filter: FindingFilter = { snapshotId: null } + ): FindingConnection @goField(forceResolver: true) trustCenterVisibility: TrustCenterVisibility! createdAt: Datetime! @@ -2707,23 +2684,32 @@ type Audit implements Node { permission(action: String!): Boolean! @goField(forceResolver: true) } -type Nonconformity implements Node { +type Finding implements Node { id: ID! snapshotId: ID - organization: Organization! @goField(forceResolver: true) + organization: Organization @goField(forceResolver: true) + kind: FindingKind! referenceId: String! description: String - audit: Audit @goField(forceResolver: true) - dateIdentified: Datetime - rootCause: String! + source: String + audits( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: AuditOrder + ): AuditConnection @goField(forceResolver: true) + identifiedOn: Datetime + rootCause: String correctiveAction: String - owner: Profile! @goField(forceResolver: true) + owner: Profile @goField(forceResolver: true) dueDate: Datetime - status: NonconformityStatus! + status: FindingStatus! + priority: FindingPriority! + risk: Risk @goField(forceResolver: true) effectivenessCheck: String createdAt: Datetime! updatedAt: Datetime! - permission(action: String!): Boolean! @goField(forceResolver: true) } @@ -2748,23 +2734,6 @@ type Obligation implements Node { permission(action: String!): Boolean! @goField(forceResolver: true) } -type ContinualImprovement implements Node { - id: ID! - snapshotId: ID - sourceId: ID - organization: Organization! @goField(forceResolver: true) - referenceId: String! - description: String - source: String - owner: Profile! @goField(forceResolver: true) - targetDate: Datetime - status: ContinualImprovementStatus! - priority: ContinualImprovementPriority! - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} type RightsRequest implements Node { id: ID! @@ -3327,18 +3296,21 @@ type AuditEdge { node: Audit! } -type NonconformityConnection +type FindingConnection @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.NonconformityConnection" + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.FindingConnection" ) { totalCount: Int! @goField(forceResolver: true) - edges: [NonconformityEdge!]! + edges: [FindingEdge!]! pageInfo: PageInfo! } -type NonconformityEdge { +type FindingEdge + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.FindingEdge" + ) { cursor: CursorKey! - node: Nonconformity! + node: Finding! } type ObligationConnection @goModel( @@ -3354,19 +3326,6 @@ type ObligationEdge { node: Obligation! } -type ContinualImprovementConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.ContinualImprovementConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [ContinualImprovementEdge!]! - pageInfo: PageInfo! -} - -type ContinualImprovementEdge { - cursor: CursorKey! - node: ContinualImprovement! -} type RightsRequestConnection @goModel( @@ -3605,10 +3564,10 @@ type Mutation { ): DeleteApplicabilityStatementPayload! createControlAuditMapping( input: CreateControlAuditMappingInput! - ): CreateControlAuditMappingPayload! + ): CreateControlAuditMappingPayload deleteControlAuditMapping( input: DeleteControlAuditMappingInput! - ): DeleteControlAuditMappingPayload! + ): DeleteControlAuditMappingPayload createControlObligationMapping( input: CreateControlObligationMappingInput! ): CreateControlObligationMappingPayload! @@ -3771,36 +3730,25 @@ type Mutation { createDatum(input: CreateDatumInput!): CreateDatumPayload! updateDatum(input: UpdateDatumInput!): UpdateDatumPayload! deleteDatum(input: DeleteDatumInput!): DeleteDatumPayload! - createAudit(input: CreateAuditInput!): CreateAuditPayload! - updateAudit(input: UpdateAuditInput!): UpdateAuditPayload! - deleteAudit(input: DeleteAuditInput!): DeleteAuditPayload! - uploadAuditReport(input: UploadAuditReportInput!): UploadAuditReportPayload! - deleteAuditReport(input: DeleteAuditReportInput!): DeleteAuditReportPayload! - # Nonconformity mutations - createNonconformity( - input: CreateNonconformityInput! - ): CreateNonconformityPayload! - updateNonconformity( - input: UpdateNonconformityInput! - ): UpdateNonconformityPayload! - deleteNonconformity( - input: DeleteNonconformityInput! - ): DeleteNonconformityPayload! + createAudit(input: CreateAuditInput!): CreateAuditPayload + updateAudit(input: UpdateAuditInput!): UpdateAuditPayload + deleteAudit(input: DeleteAuditInput!): DeleteAuditPayload + uploadAuditReport(input: UploadAuditReportInput!): UploadAuditReportPayload + deleteAuditReport(input: DeleteAuditReportInput!): DeleteAuditReportPayload + # Finding mutations + createFinding(input: CreateFindingInput!): CreateFindingPayload + updateFinding(input: UpdateFindingInput!): UpdateFindingPayload + deleteFinding(input: DeleteFindingInput!): DeleteFindingPayload + createFindingAuditMapping( + input: CreateFindingAuditMappingInput! + ): CreateFindingAuditMappingPayload + deleteFindingAuditMapping( + input: DeleteFindingAuditMappingInput! + ): DeleteFindingAuditMappingPayload # Obligation mutations createObligation(input: CreateObligationInput!): CreateObligationPayload! updateObligation(input: UpdateObligationInput!): UpdateObligationPayload! deleteObligation(input: DeleteObligationInput!): DeleteObligationPayload! - # Continual Improvement mutations - createContinualImprovement( - input: CreateContinualImprovementInput! - ): CreateContinualImprovementPayload! - updateContinualImprovement( - input: UpdateContinualImprovementInput! - ): UpdateContinualImprovementPayload! - deleteContinualImprovement( - input: DeleteContinualImprovementInput! - ): DeleteContinualImprovementPayload! - # Rights Request mutations createRightsRequest( input: CreateRightsRequestInput! @@ -4535,37 +4483,51 @@ input DeleteAuditReportInput { auditId: ID! } -# Nonconformity input types -input CreateNonconformityInput { +# Finding input types +input CreateFindingInput { organizationId: ID! - referenceId: String! + kind: FindingKind! description: String - auditId: ID - dateIdentified: Datetime - rootCause: String! + source: String + identifiedOn: Datetime + rootCause: String correctiveAction: String - ownerId: ID! + ownerId: ID dueDate: Datetime - status: NonconformityStatus! + status: FindingStatus! + priority: FindingPriority! + riskId: ID effectivenessCheck: String } -input UpdateNonconformityInput { +input UpdateFindingInput { id: ID! - referenceId: String description: String @goField(omittable: true) - dateIdentified: Datetime @goField(omittable: true) - rootCause: String + source: String @goField(omittable: true) + identifiedOn: Datetime @goField(omittable: true) + rootCause: String @goField(omittable: true) correctiveAction: String @goField(omittable: true) ownerId: ID - auditId: ID @goField(omittable: true) dueDate: Datetime @goField(omittable: true) - status: NonconformityStatus + status: FindingStatus + priority: FindingPriority + riskId: ID @goField(omittable: true) effectivenessCheck: String @goField(omittable: true) } -input DeleteNonconformityInput { - nonconformityId: ID! +input DeleteFindingInput { + findingId: ID! +} + +input CreateFindingAuditMappingInput { + findingId: ID! + auditId: ID! + referenceId: String! +} + +input DeleteFindingAuditMappingInput { + findingId: ID! + auditId: ID! } input CreateObligationInput { @@ -4600,31 +4562,6 @@ input DeleteObligationInput { obligationId: ID! } -input CreateContinualImprovementInput { - organizationId: ID! - referenceId: String! - description: String - source: String - ownerId: ID! - targetDate: Datetime - status: ContinualImprovementStatus! - priority: ContinualImprovementPriority! -} - -input UpdateContinualImprovementInput { - id: ID! - referenceId: String - description: String @goField(omittable: true) - source: String @goField(omittable: true) - ownerId: ID - targetDate: Datetime @goField(omittable: true) - status: ContinualImprovementStatus - priority: ContinualImprovementPriority -} - -input DeleteContinualImprovementInput { - continualImprovementId: ID! -} input CreateRightsRequestInput { organizationId: ID! @@ -4996,13 +4933,13 @@ type DeleteApplicabilityStatementPayload { } type CreateControlAuditMappingPayload { - controlEdge: ControlEdge! - auditEdge: AuditEdge! + controlEdge: ControlEdge + auditEdge: AuditEdge } type DeleteControlAuditMappingPayload { - deletedControlId: ID! - deletedAuditId: ID! + deletedControlId: ID + deletedAuditId: ID } type CreateControlObligationMappingPayload { @@ -5616,36 +5553,46 @@ type DeleteDatumPayload { } type CreateAuditPayload { - auditEdge: AuditEdge! + auditEdge: AuditEdge } type UpdateAuditPayload { - audit: Audit! + audit: Audit } type DeleteAuditPayload { - deletedAuditId: ID! + deletedAuditId: ID } type UploadAuditReportPayload { - audit: Audit! + audit: Audit } type DeleteAuditReportPayload { - audit: Audit! + audit: Audit } -# Nonconformity payload types -type CreateNonconformityPayload { - nonconformityEdge: NonconformityEdge! +# Finding payload types +type CreateFindingPayload { + findingEdge: FindingEdge } -type UpdateNonconformityPayload { - nonconformity: Nonconformity! +type UpdateFindingPayload { + finding: Finding } -type DeleteNonconformityPayload { - deletedNonconformityId: ID! +type DeleteFindingPayload { + deletedFindingId: ID +} + +type CreateFindingAuditMappingPayload { + findingEdge: FindingEdge + auditEdge: AuditEdge +} + +type DeleteFindingAuditMappingPayload { + deletedFindingId: ID + deletedAuditId: ID } type CreateObligationPayload { @@ -5660,17 +5607,6 @@ type DeleteObligationPayload { deletedObligationId: ID! } -type CreateContinualImprovementPayload { - continualImprovementEdge: ContinualImprovementEdge! -} - -type UpdateContinualImprovementPayload { - continualImprovement: ContinualImprovement! -} - -type DeleteContinualImprovementPayload { - deletedContinualImprovementId: ID! -} type CreateRightsRequestPayload { rightsRequestEdge: RightsRequestEdge! diff --git a/pkg/server/api/console/v1/types/continual_improvement.go b/pkg/server/api/console/v1/types/continual_improvement.go deleted file mode 100644 index 925e53bd1..000000000 --- a/pkg/server/api/console/v1/types/continual_improvement.go +++ /dev/null @@ -1,85 +0,0 @@ -// 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 types - -import ( - "go.probo.inc/probo/pkg/coredata" - "go.probo.inc/probo/pkg/gid" - "go.probo.inc/probo/pkg/page" -) - -type ( - ContinualImprovementOrderBy OrderBy[coredata.ContinualImprovementOrderField] - - ContinualImprovementConnection struct { - TotalCount int - Edges []*ContinualImprovementEdge - PageInfo PageInfo - - Resolver any - ParentID gid.GID - Filter *ContinualImprovementFilter - } -) - -func NewContinualImprovementConnection( - p *page.Page[*coredata.ContinualImprovement, coredata.ContinualImprovementOrderField], - parentType any, - parentID gid.GID, - filter *ContinualImprovementFilter, -) *ContinualImprovementConnection { - edges := make([]*ContinualImprovementEdge, len(p.Data)) - for i, improvement := range p.Data { - edges[i] = NewContinualImprovementEdge(improvement, p.Cursor.OrderBy.Field) - } - - return &ContinualImprovementConnection{ - Edges: edges, - PageInfo: *NewPageInfo(p), - - Resolver: parentType, - ParentID: parentID, - Filter: filter, - } -} - -func NewContinualImprovementEdge(ci *coredata.ContinualImprovement, orderField coredata.ContinualImprovementOrderField) *ContinualImprovementEdge { - return &ContinualImprovementEdge{ - Node: NewContinualImprovement(ci), - Cursor: ci.CursorKey(orderField), - } -} - -func NewContinualImprovement(ci *coredata.ContinualImprovement) *ContinualImprovement { - return &ContinualImprovement{ - ID: ci.ID, - SnapshotID: ci.SnapshotID, - Organization: &Organization{ - ID: ci.OrganizationID, - }, - Owner: &Profile{ - ID: ci.OwnerID, - }, - SourceID: ci.SourceID, - ReferenceID: ci.ReferenceID, - Description: ci.Description, - Source: ci.Source, - TargetDate: ci.TargetDate, - Status: ci.Status, - Priority: ci.Priority, - CreatedAt: ci.CreatedAt, - UpdatedAt: ci.UpdatedAt, - } -} diff --git a/pkg/server/api/console/v1/types/finding.go b/pkg/server/api/console/v1/types/finding.go new file mode 100644 index 000000000..2c3f48369 --- /dev/null +++ b/pkg/server/api/console/v1/types/finding.go @@ -0,0 +1,103 @@ +// Copyright (c) 2026 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 types + +import ( + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/page" +) + +type ( + FindingEdge struct { + Cursor page.CursorKey `json:"cursor"` + Node *Finding `json:"node"` + } + + FindingConnection struct { + TotalCount int + Edges []*FindingEdge + PageInfo PageInfo + + Resolver any + ParentID gid.GID + Filter *FindingFilter + } +) + +func NewFindingConnection( + p *page.Page[*coredata.Finding, coredata.FindingOrderField], + parentType any, + parentID gid.GID, + filter *FindingFilter, +) *FindingConnection { + edges := make([]*FindingEdge, len(p.Data)) + for i, finding := range p.Data { + edges[i] = NewFindingEdge(finding, p.Cursor.OrderBy.Field) + } + + return &FindingConnection{ + Edges: edges, + PageInfo: *NewPageInfo(p), + + Resolver: parentType, + ParentID: parentID, + Filter: filter, + } +} + +func NewFindingEdge(f *coredata.Finding, orderField coredata.FindingOrderField) *FindingEdge { + return &FindingEdge{ + Node: NewFinding(f), + Cursor: f.CursorKey(orderField), + } +} + +func NewFinding(f *coredata.Finding) *Finding { + finding := &Finding{ + ID: f.ID, + SnapshotID: f.SnapshotID, + Organization: &Organization{ + ID: f.OrganizationID, + }, + Kind: f.Kind, + ReferenceID: f.ReferenceID, + Description: f.Description, + Source: f.Source, + IdentifiedOn: f.IdentifiedOn, + RootCause: f.RootCause, + CorrectiveAction: f.CorrectiveAction, + DueDate: f.DueDate, + Status: f.Status, + Priority: f.Priority, + EffectivenessCheck: f.EffectivenessCheck, + CreatedAt: f.CreatedAt, + UpdatedAt: f.UpdatedAt, + } + + if f.OwnerID != nil { + finding.Owner = &Profile{ + ID: *f.OwnerID, + } + } + + if f.RiskID != nil { + finding.Risk = &Risk{ + ID: *f.RiskID, + } + } + + return finding +} diff --git a/pkg/server/api/console/v1/types/nonconformity.go b/pkg/server/api/console/v1/types/nonconformity.go deleted file mode 100644 index ec1c5cc49..000000000 --- a/pkg/server/api/console/v1/types/nonconformity.go +++ /dev/null @@ -1,94 +0,0 @@ -// 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 types - -import ( - "go.probo.inc/probo/pkg/coredata" - "go.probo.inc/probo/pkg/gid" - "go.probo.inc/probo/pkg/page" -) - -type ( - NonconformityOrderBy OrderBy[coredata.NonconformityOrderField] - - NonconformityConnection struct { - TotalCount int - Edges []*NonconformityEdge - PageInfo PageInfo - - Resolver any - ParentID gid.GID - Filter *NonconformityFilter - } -) - -func NewNonconformityConnection( - p *page.Page[*coredata.Nonconformity, coredata.NonconformityOrderField], - parentType any, - parentID gid.GID, - filter *NonconformityFilter, -) *NonconformityConnection { - edges := make([]*NonconformityEdge, len(p.Data)) - for i, nonconformity := range p.Data { - edges[i] = NewNonconformityEdge(nonconformity, p.Cursor.OrderBy.Field) - } - - return &NonconformityConnection{ - Edges: edges, - PageInfo: *NewPageInfo(p), - - Resolver: parentType, - ParentID: parentID, - Filter: filter, - } -} - -func NewNonconformityEdge(nr *coredata.Nonconformity, orderField coredata.NonconformityOrderField) *NonconformityEdge { - return &NonconformityEdge{ - Node: NewNonconformity(nr), - Cursor: nr.CursorKey(orderField), - } -} - -func NewNonconformity(nr *coredata.Nonconformity) *Nonconformity { - nc := &Nonconformity{ - ID: nr.ID, - Organization: &Organization{ - ID: nr.OrganizationID, - }, - Owner: &Profile{ - ID: nr.OwnerID, - }, - ReferenceID: nr.ReferenceID, - SnapshotID: nr.SnapshotID, - Description: nr.Description, - DateIdentified: nr.DateIdentified, - RootCause: nr.RootCause, - CorrectiveAction: nr.CorrectiveAction, - DueDate: nr.DueDate, - Status: nr.Status, - EffectivenessCheck: nr.EffectivenessCheck, - CreatedAt: nr.CreatedAt, - UpdatedAt: nr.UpdatedAt, - } - - if nr.AuditID != nil { - nc.Audit = &Audit{ - ID: *nr.AuditID, - } - } - - return nc -} diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go index a89e78466..a5c44d871 100644 --- a/pkg/server/api/console/v1/v1_resolver.go +++ b/pkg/server/api/console/v1/v1_resolver.go @@ -304,6 +304,54 @@ func (r *auditResolver) Controls(ctx context.Context, obj *types.Audit, first *i return types.NewControlConnection(page, r, obj.ID, controlFilter), nil } +// Findings is the resolver for the findings field. +func (r *auditResolver) Findings(ctx context.Context, obj *types.Audit, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.FindingOrder, filter *types.FindingFilter) (*types.FindingConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionFindingList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.FindingOrderField]{ + Field: coredata.FindingOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.FindingOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var ( + kind *coredata.FindingKind + status *coredata.FindingStatus + priority *coredata.FindingPriority + ownerID *gid.GID + ) + if filter != nil { + kind = filter.Kind + status = filter.Status + priority = filter.Priority + ownerID = filter.OwnerID + } + + findingFilter := coredata.NewFindingFilter(nil, kind, status, priority, ownerID) + if filter != nil { + findingFilter = coredata.NewFindingFilter(&filter.SnapshotID, kind, status, priority, ownerID) + } + + p, err := prb.Findings.ListForAuditID(ctx, obj.ID, cursor, findingFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list audit findings", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewFindingConnection(p, r, obj.ID, filter), nil +} + // Permission is the resolver for the permission field. func (r *auditResolver) Permission(ctx context.Context, obj *types.Audit, action string) (bool, error) { return r.Resolver.Permission(ctx, obj, action) @@ -315,15 +363,34 @@ func (r *auditConnectionResolver) TotalCount(ctx context.Context, obj *types.Aud return 0, err } - // TODO missing switch case - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - count, err := prb.Audits.CountForOrganizationID(ctx, obj.ParentID) - if err != nil { - panic(fmt.Errorf("cannot count audits: %w", err)) + switch obj.Resolver.(type) { + case *organizationResolver: + count, err := prb.Audits.CountForOrganizationID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count audits", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *findingResolver: + count, err := prb.Audits.CountForFindingID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count audits", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *controlResolver: + count, err := prb.Audits.CountForControlID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count audits", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + default: + r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver)) + return 0, gqlutils.Internal(ctx) } - return count, nil } // Permission is the resolver for the permission field. @@ -348,75 +415,6 @@ func (r *complianceFrameworkResolver) Framework(ctx context.Context, obj *types. return types.NewFramework(framework), nil } -// Organization is the resolver for the organization field. -func (r *continualImprovementResolver) Organization(ctx context.Context, obj *types.ContinualImprovement) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - organization, err := prb.Organizations.Get(ctx, obj.Organization.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - panic(fmt.Errorf("cannot get continual improvement organization: %w", err)) - } - - return types.NewOrganization(organization), nil -} - -// Owner is the resolver for the owner field. -func (r *continualImprovementResolver) Owner(ctx context.Context, obj *types.ContinualImprovement) (*types.Profile, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { - return nil, err - } - - owner, err := r.iam.OrganizationService.GetProfile(ctx, obj.Owner.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - panic(fmt.Errorf("cannot get continual improvement owner: %w", err)) - } - - return types.NewProfile(owner), nil -} - -// Permission is the resolver for the permission field. -func (r *continualImprovementResolver) Permission(ctx context.Context, obj *types.ContinualImprovement, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *continualImprovementConnectionResolver) TotalCount(ctx context.Context, obj *types.ContinualImprovementConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionContinualImprovementList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - - switch obj.Resolver.(type) { - case *organizationResolver: - continualImprovementFilter := coredata.NewContinualImprovementFilter(nil) - if obj.Filter != nil { - continualImprovementFilter = coredata.NewContinualImprovementFilter(&obj.Filter.SnapshotID) - } - - count, err := prb.ContinualImprovements.CountByOrganizationID(ctx, obj.ParentID, continualImprovementFilter) - if err != nil { - panic(fmt.Errorf("cannot count continual improvements: %w", err)) - } - return count, nil - } - - // TODO no panic use gqlutils.InternalError - panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver)) -} - // Organization is the resolver for the organization field. func (r *controlResolver) Organization(ctx context.Context, obj *types.Control) (*types.Organization, error) { if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { @@ -1421,6 +1419,157 @@ func (r *fileResolver) DownloadURL(ctx context.Context, obj *types.File) (string return downloadUrl, nil } +// Organization is the resolver for the organization field. +func (r *findingResolver) Organization(ctx context.Context, obj *types.Finding) (*types.Organization, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + organization, err := prb.Organizations.Get(ctx, obj.Organization.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get finding organization", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewOrganization(organization), nil +} + +// Audits is the resolver for the audits field. +func (r *findingResolver) Audits(ctx context.Context, obj *types.Finding, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditOrderBy) (*types.AuditConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionAuditList); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.AuditOrderField]{ + Field: coredata.AuditOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.AuditOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + p, err := prb.Audits.ListForFindingID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list finding audits", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewAuditConnection(p, r, obj.ID), nil +} + +// Owner is the resolver for the owner field. +func (r *findingResolver) Owner(ctx context.Context, obj *types.Finding) (*types.Profile, error) { + if obj.Owner == nil { + return nil, nil + } + + if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { + return nil, err + } + + owner, err := r.iam.OrganizationService.GetProfile(ctx, obj.Owner.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get finding owner", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewProfile(owner), nil +} + +// Risk is the resolver for the risk field. +func (r *findingResolver) Risk(ctx context.Context, obj *types.Finding) (*types.Risk, error) { + if obj.Risk == nil { + return nil, nil + } + + if err := r.authorize(ctx, obj.ID, probo.ActionRiskGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + risk, err := prb.Risks.Get(ctx, obj.Risk.ID) + if err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return nil, gqlutils.NotFound(ctx, err) + } + + r.logger.ErrorCtx(ctx, "cannot get finding risk", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewRisk(risk), nil +} + +// Permission is the resolver for the permission field. +func (r *findingResolver) Permission(ctx context.Context, obj *types.Finding, action string) (bool, error) { + return r.Resolver.Permission(ctx, obj, action) +} + +// TotalCount is the resolver for the totalCount field. +func (r *findingConnectionResolver) TotalCount(ctx context.Context, obj *types.FindingConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionFindingList); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + var ( + kind *coredata.FindingKind + status *coredata.FindingStatus + priority *coredata.FindingPriority + ownerID *gid.GID + ) + if obj.Filter != nil { + kind = obj.Filter.Kind + status = obj.Filter.Status + priority = obj.Filter.Priority + ownerID = obj.Filter.OwnerID + } + + findingFilter := coredata.NewFindingFilter(nil, kind, status, priority, ownerID) + if obj.Filter != nil { + findingFilter = coredata.NewFindingFilter(&obj.Filter.SnapshotID, kind, status, priority, ownerID) + } + + switch obj.Resolver.(type) { + case *organizationResolver: + count, err := prb.Findings.CountForOrganizationID(ctx, obj.ParentID, findingFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count findings", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + case *auditResolver: + count, err := prb.Findings.CountForAuditID(ctx, obj.ParentID, findingFilter) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count findings", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + return count, nil + } + + r.logger.ErrorCtx(ctx, "unsupported resolver", log.Any("resolver", obj.Resolver)) + return 0, gqlutils.Internal(ctx) +} + // Organization is the resolver for the organization field. func (r *frameworkResolver) Organization(ctx context.Context, obj *types.Framework) (*types.Organization, error) { if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { @@ -3302,8 +3451,8 @@ func (r *mutationResolver) DeleteControlAuditMapping(ctx context.Context, input } return &types.DeleteControlAuditMappingPayload{ - DeletedControlID: control.ID, - DeletedAuditID: audit.ID, + DeletedControlID: &control.ID, + DeletedAuditID: &audit.ID, }, nil } @@ -5027,7 +5176,7 @@ func (r *mutationResolver) DeleteAudit(ctx context.Context, input types.DeleteAu } return &types.DeleteAuditPayload{ - DeletedAuditID: input.AuditID, + DeletedAuditID: &input.AuditID, }, nil } @@ -5079,88 +5228,131 @@ func (r *mutationResolver) DeleteAuditReport(ctx context.Context, input types.De }, nil } -// CreateNonconformity is the resolver for the createNonconformity field. -func (r *mutationResolver) CreateNonconformity(ctx context.Context, input types.CreateNonconformityInput) (*types.CreateNonconformityPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionNonconformityCreate); err != nil { +// CreateFinding is the resolver for the createFinding field. +func (r *mutationResolver) CreateFinding(ctx context.Context, input types.CreateFindingInput) (*types.CreateFindingPayload, error) { + if err := r.authorize(ctx, input.OrganizationID, probo.ActionFindingCreate); err != nil { return nil, err } prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - req := probo.CreateNonconformityRequest{ + req := probo.CreateFindingRequest{ OrganizationID: input.OrganizationID, - ReferenceID: input.ReferenceID, + Kind: input.Kind, Description: input.Description, - AuditID: input.AuditID, - DateIdentified: input.DateIdentified, + Source: input.Source, + IdentifiedOn: input.IdentifiedOn, RootCause: input.RootCause, CorrectiveAction: input.CorrectiveAction, OwnerID: input.OwnerID, DueDate: input.DueDate, Status: &input.Status, + Priority: &input.Priority, + RiskID: input.RiskID, EffectivenessCheck: input.EffectivenessCheck, } - nonconformity, err := prb.Nonconformities.Create(ctx, &req) + finding, err := prb.Findings.Create(ctx, &req) if err != nil { - // TODO no panic use gqlutils.InternalError - panic(fmt.Errorf("cannot create nonconformity: %w", err)) + r.logger.ErrorCtx(ctx, "cannot create finding", log.Error(err)) + return nil, gqlutils.Internal(ctx) } - return &types.CreateNonconformityPayload{ - NonconformityEdge: types.NewNonconformityEdge(nonconformity, coredata.NonconformityOrderFieldCreatedAt), + return &types.CreateFindingPayload{ + FindingEdge: types.NewFindingEdge(finding, coredata.FindingOrderFieldCreatedAt), }, nil } -// UpdateNonconformity is the resolver for the updateNonconformity field. -func (r *mutationResolver) UpdateNonconformity(ctx context.Context, input types.UpdateNonconformityInput) (*types.UpdateNonconformityPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionNonconformityUpdate); err != nil { +// UpdateFinding is the resolver for the updateFinding field. +func (r *mutationResolver) UpdateFinding(ctx context.Context, input types.UpdateFindingInput) (*types.UpdateFindingPayload, error) { + if err := r.authorize(ctx, input.ID, probo.ActionFindingUpdate); err != nil { return nil, err } prb := r.ProboService(ctx, input.ID.TenantID()) - req := probo.UpdateNonconformityRequest{ + req := probo.UpdateFindingRequest{ ID: input.ID, - ReferenceID: input.ReferenceID, Description: gqlutils.UnwrapOmittable(input.Description), - DateIdentified: gqlutils.UnwrapOmittable(input.DateIdentified), - RootCause: input.RootCause, + Source: gqlutils.UnwrapOmittable(input.Source), + IdentifiedOn: gqlutils.UnwrapOmittable(input.IdentifiedOn), + RootCause: gqlutils.UnwrapOmittable(input.RootCause), CorrectiveAction: gqlutils.UnwrapOmittable(input.CorrectiveAction), OwnerID: input.OwnerID, - AuditID: gqlutils.UnwrapOmittable(input.AuditID), DueDate: gqlutils.UnwrapOmittable(input.DueDate), Status: input.Status, + Priority: input.Priority, + RiskID: gqlutils.UnwrapOmittable(input.RiskID), EffectivenessCheck: gqlutils.UnwrapOmittable(input.EffectivenessCheck), } - nonconformity, err := prb.Nonconformities.Update(ctx, &req) + finding, err := prb.Findings.Update(ctx, &req) if err != nil { - // TODO no panic use gqlutils.InternalError - panic(fmt.Errorf("cannot update nonconformity: %w", err)) + r.logger.ErrorCtx(ctx, "cannot update finding", log.Error(err)) + return nil, gqlutils.Internal(ctx) } - return &types.UpdateNonconformityPayload{ - Nonconformity: types.NewNonconformity(nonconformity), + return &types.UpdateFindingPayload{ + Finding: types.NewFinding(finding), }, nil } -// DeleteNonconformity is the resolver for the deleteNonconformity field. -func (r *mutationResolver) DeleteNonconformity(ctx context.Context, input types.DeleteNonconformityInput) (*types.DeleteNonconformityPayload, error) { - if err := r.authorize(ctx, input.NonconformityID, probo.ActionNonconformityDelete); err != nil { +// DeleteFinding is the resolver for the deleteFinding field. +func (r *mutationResolver) DeleteFinding(ctx context.Context, input types.DeleteFindingInput) (*types.DeleteFindingPayload, error) { + if err := r.authorize(ctx, input.FindingID, probo.ActionFindingDelete); err != nil { return nil, err } - prb := r.ProboService(ctx, input.NonconformityID.TenantID()) + prb := r.ProboService(ctx, input.FindingID.TenantID()) - err := prb.Nonconformities.Delete(ctx, input.NonconformityID) + err := prb.Findings.Delete(ctx, input.FindingID) if err != nil { - // TODO no panic use gqlutils.InternalError - panic(fmt.Errorf("cannot delete nonconformity: %w", err)) + r.logger.ErrorCtx(ctx, "cannot delete finding", log.Error(err)) + return nil, gqlutils.Internal(ctx) } - return &types.DeleteNonconformityPayload{ - DeletedNonconformityID: input.NonconformityID, + return &types.DeleteFindingPayload{ + DeletedFindingID: &input.FindingID, + }, nil +} + +// CreateFindingAuditMapping is the resolver for the createFindingAuditMapping field. +func (r *mutationResolver) CreateFindingAuditMapping(ctx context.Context, input types.CreateFindingAuditMappingInput) (*types.CreateFindingAuditMappingPayload, error) { + if err := r.authorize(ctx, input.FindingID, probo.ActionFindingAuditMappingCreate); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.FindingID.TenantID()) + + finding, audit, err := prb.Findings.CreateAuditMapping(ctx, input.FindingID, input.AuditID, input.ReferenceID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot create finding audit mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateFindingAuditMappingPayload{ + FindingEdge: types.NewFindingEdge(finding, coredata.FindingOrderFieldCreatedAt), + AuditEdge: types.NewAuditEdge(audit, coredata.AuditOrderFieldCreatedAt), + }, nil +} + +// DeleteFindingAuditMapping is the resolver for the deleteFindingAuditMapping field. +func (r *mutationResolver) DeleteFindingAuditMapping(ctx context.Context, input types.DeleteFindingAuditMappingInput) (*types.DeleteFindingAuditMappingPayload, error) { + if err := r.authorize(ctx, input.FindingID, probo.ActionFindingAuditMappingDelete); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, input.FindingID.TenantID()) + + finding, audit, err := prb.Findings.DeleteAuditMapping(ctx, input.FindingID, input.AuditID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot delete finding audit mapping", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.DeleteFindingAuditMappingPayload{ + DeletedFindingID: &finding.ID, + DeletedAuditID: &audit.ID, }, nil } @@ -5249,85 +5441,6 @@ func (r *mutationResolver) DeleteObligation(ctx context.Context, input types.Del }, nil } -// CreateContinualImprovement is the resolver for the createContinualImprovement field. -func (r *mutationResolver) CreateContinualImprovement(ctx context.Context, input types.CreateContinualImprovementInput) (*types.CreateContinualImprovementPayload, error) { - if err := r.authorize(ctx, input.OrganizationID, probo.ActionContinualImprovementCreate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.OrganizationID.TenantID()) - - req := probo.CreateContinualImprovementRequest{ - OrganizationID: input.OrganizationID, - ReferenceID: input.ReferenceID, - Description: input.Description, - Source: input.Source, - OwnerID: input.OwnerID, - TargetDate: input.TargetDate, - Status: &input.Status, - Priority: &input.Priority, - } - - continualImprovement, err := prb.ContinualImprovements.Create(ctx, &req) - if err != nil { - // TODO no panic use gqlutils.InternalError - panic(fmt.Errorf("cannot create continual improvement: %w", err)) - } - - return &types.CreateContinualImprovementPayload{ - ContinualImprovementEdge: types.NewContinualImprovementEdge(continualImprovement, coredata.ContinualImprovementOrderFieldCreatedAt), - }, nil -} - -// UpdateContinualImprovement is the resolver for the updateContinualImprovement field. -func (r *mutationResolver) UpdateContinualImprovement(ctx context.Context, input types.UpdateContinualImprovementInput) (*types.UpdateContinualImprovementPayload, error) { - if err := r.authorize(ctx, input.ID, probo.ActionContinualImprovementUpdate); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ID.TenantID()) - - req := probo.UpdateContinualImprovementRequest{ - ID: input.ID, - ReferenceID: input.ReferenceID, - Description: gqlutils.UnwrapOmittable(input.Description), - Source: gqlutils.UnwrapOmittable(input.Source), - OwnerID: input.OwnerID, - TargetDate: gqlutils.UnwrapOmittable(input.TargetDate), - Status: input.Status, - Priority: input.Priority, - } - - continualImprovement, err := prb.ContinualImprovements.Update(ctx, &req) - if err != nil { - // TODO no panic use gqlutils.InternalError - panic(fmt.Errorf("cannot update continual improvement: %w", err)) - } - - return &types.UpdateContinualImprovementPayload{ - ContinualImprovement: types.NewContinualImprovement(continualImprovement), - }, nil -} - -// DeleteContinualImprovement is the resolver for the deleteContinualImprovement field. -func (r *mutationResolver) DeleteContinualImprovement(ctx context.Context, input types.DeleteContinualImprovementInput) (*types.DeleteContinualImprovementPayload, error) { - if err := r.authorize(ctx, input.ContinualImprovementID, probo.ActionContinualImprovementDelete); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, input.ContinualImprovementID.TenantID()) - - err := prb.ContinualImprovements.Delete(ctx, input.ContinualImprovementID) - if err != nil { - // TODO no panic use gqlutils.InternalError - panic(fmt.Errorf("cannot delete continual improvement: %w", err)) - } - - return &types.DeleteContinualImprovementPayload{ - DeletedContinualImprovementID: input.ContinualImprovementID, - }, nil -} - // CreateRightsRequest is the resolver for the createRightsRequest field. func (r *mutationResolver) CreateRightsRequest(ctx context.Context, input types.CreateRightsRequestInput) (*types.CreateRightsRequestPayload, error) { if err := r.authorize(ctx, input.OrganizationID, probo.ActionRightsRequestCreate); err != nil { @@ -5763,102 +5876,6 @@ func (r *mutationResolver) DeleteCustomDomain(ctx context.Context, input types.D }, nil } -// Organization is the resolver for the organization field. -func (r *nonconformityResolver) Organization(ctx context.Context, obj *types.Nonconformity) (*types.Organization, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - organization, err := prb.Organizations.Get(ctx, obj.Organization.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - // TODO no panic use gqlutils.InternalError - panic(fmt.Errorf("cannot get nonconformity organization: %w", err)) - } - - return types.NewOrganization(organization), nil -} - -// Audit is the resolver for the audit field. -func (r *nonconformityResolver) Audit(ctx context.Context, obj *types.Nonconformity) (*types.Audit, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionAuditGet); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - if obj.Audit == nil { - return nil, nil - } - - audit, err := prb.Audits.Get(ctx, obj.Audit.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - // TODO no panic use gqlutils.InternalError - panic(fmt.Errorf("cannot get nonconformity audit: %w", err)) - } - - return types.NewAudit(audit), nil -} - -// Owner is the resolver for the owner field. -func (r *nonconformityResolver) Owner(ctx context.Context, obj *types.Nonconformity) (*types.Profile, error) { - if err := r.authorize(ctx, obj.ID, iam.ActionMembershipProfileGet); err != nil { - return nil, err - } - - owner, err := r.iam.OrganizationService.GetProfile(ctx, obj.Owner.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - - // TODO no panic use gqlutils.InternalError - panic(fmt.Errorf("cannot get nonconformity owner: %w", err)) - } - - return types.NewProfile(owner), nil -} - -// Permission is the resolver for the permission field. -func (r *nonconformityResolver) Permission(ctx context.Context, obj *types.Nonconformity, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *nonconformityConnectionResolver) TotalCount(ctx context.Context, obj *types.NonconformityConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionNonconformityList); err != nil { - return 0, err - } - - prb := r.ProboService(ctx, obj.ParentID.TenantID()) - switch obj.Resolver.(type) { - case *organizationResolver: - nonconformityFilter := coredata.NewNonconformityFilter(nil) - if obj.Filter != nil { - nonconformityFilter = coredata.NewNonconformityFilter(&obj.Filter.SnapshotID) - } - - count, err := prb.Nonconformities.CountForOrganizationID(ctx, obj.ParentID, nonconformityFilter) - if err != nil { - // TODO no panic use gqlutils.InternalError - panic(fmt.Errorf("cannot count nonconformities: %w", err)) - } - return count, nil - } - - // TODO no panic use gqlutils.InternalError - panic(fmt.Errorf("unsupported resolver: %T", obj.Resolver)) -} - // Organization is the resolver for the organization field. func (r *obligationResolver) Organization(ctx context.Context, obj *types.Obligation) (*types.Organization, error) { if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { @@ -6458,21 +6475,21 @@ func (r *organizationResolver) Audits(ctx context.Context, obj *types.Organizati return types.NewAuditConnection(page, r, obj.ID), nil } -// Nonconformities is the resolver for the nonconformities field. -func (r *organizationResolver) Nonconformities(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.NonconformityOrderBy, filter *types.NonconformityFilter) (*types.NonconformityConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionNonconformityList); err != nil { +// Findings is the resolver for the findings field. +func (r *organizationResolver) Findings(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.FindingOrder, filter *types.FindingFilter) (*types.FindingConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionFindingList); err != nil { return nil, err } prb := r.ProboService(ctx, obj.ID.TenantID()) - pageOrderBy := page.OrderBy[coredata.NonconformityOrderField]{ - Field: coredata.NonconformityOrderFieldCreatedAt, + pageOrderBy := page.OrderBy[coredata.FindingOrderField]{ + Field: coredata.FindingOrderFieldCreatedAt, Direction: page.OrderDirectionDesc, } if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.NonconformityOrderField]{ + pageOrderBy = page.OrderBy[coredata.FindingOrderField]{ Field: orderBy.Field, Direction: orderBy.Direction, } @@ -6480,18 +6497,31 @@ func (r *organizationResolver) Nonconformities(ctx context.Context, obj *types.O cursor := types.NewCursor(first, after, last, before, pageOrderBy) - nonconformityFilter := coredata.NewNonconformityFilter(nil) + var ( + kind *coredata.FindingKind + status *coredata.FindingStatus + priority *coredata.FindingPriority + ownerID *gid.GID + ) if filter != nil { - nonconformityFilter = coredata.NewNonconformityFilter(&filter.SnapshotID) + kind = filter.Kind + status = filter.Status + priority = filter.Priority + ownerID = filter.OwnerID } - page, err := prb.Nonconformities.ListForOrganizationID(ctx, obj.ID, cursor, nonconformityFilter) + findingFilter := coredata.NewFindingFilter(nil, kind, status, priority, ownerID) + if filter != nil { + findingFilter = coredata.NewFindingFilter(&filter.SnapshotID, kind, status, priority, ownerID) + } + + page, err := prb.Findings.ListForOrganizationID(ctx, obj.ID, cursor, findingFilter) if err != nil { - // TODO no panic use gqlutils.InternalError - panic(fmt.Errorf("cannot list organization nonconformities: %w", err)) + r.logger.ErrorCtx(ctx, "cannot list organization findings", log.Error(err)) + return nil, gqlutils.Internal(ctx) } - return types.NewNonconformityConnection(page, r, obj.ID, filter), nil + return types.NewFindingConnection(page, r, obj.ID, filter), nil } // Obligations is the resolver for the obligations field. @@ -6530,42 +6560,6 @@ func (r *organizationResolver) Obligations(ctx context.Context, obj *types.Organ return types.NewObligationConnection(page, r, obj.ID, filter), nil } -// ContinualImprovements is the resolver for the continualImprovements field. -func (r *organizationResolver) ContinualImprovements(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ContinualImprovementOrderBy, filter *types.ContinualImprovementFilter) (*types.ContinualImprovementConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionContinualImprovementList); err != nil { - return nil, err - } - - prb := r.ProboService(ctx, obj.ID.TenantID()) - - pageOrderBy := page.OrderBy[coredata.ContinualImprovementOrderField]{ - Field: coredata.ContinualImprovementOrderFieldCreatedAt, - Direction: page.OrderDirectionDesc, - } - - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.ContinualImprovementOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - - continualImprovementFilter := coredata.NewContinualImprovementFilter(nil) - if filter != nil { - continualImprovementFilter = coredata.NewContinualImprovementFilter(&filter.SnapshotID) - } - - page, err := prb.ContinualImprovements.ListForOrganizationID(ctx, obj.ID, cursor, continualImprovementFilter) - if err != nil { - // TODO no panic use gqlutils.InternalError - panic(fmt.Errorf("cannot list organization continual improvements: %w", err)) - } - - return types.NewContinualImprovementConnection(page, r, obj.ID, filter), nil -} - // RightsRequests is the resolver for the rightsRequests field. func (r *organizationResolver) RightsRequests(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.RightsRequestOrderBy) (*types.RightsRequestConnection, error) { if err := r.authorize(ctx, obj.ID, probo.ActionRightsRequestList); err != nil { @@ -7173,14 +7167,14 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error } return types.NewAudit(audit), nil } - case coredata.NonconformityEntityType: - action = probo.ActionNonconformityList + case coredata.FindingEntityType: + action = probo.ActionFindingList loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - nonconformity, err := prb.Nonconformities.Get(ctx, id) + finding, err := prb.Findings.Get(ctx, id) if err != nil { return nil, err } - return types.NewNonconformity(nonconformity), nil + return types.NewFinding(finding), nil } case coredata.ObligationEntityType: action = probo.ActionObligationList @@ -7191,15 +7185,6 @@ func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error } return types.NewObligation(obligation), nil } - case coredata.ContinualImprovementEntityType: - action = probo.ActionContinualImprovementList - loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { - continualImprovement, err := prb.ContinualImprovements.Get(ctx, id) - if err != nil { - return nil, err - } - return types.NewContinualImprovement(continualImprovement), nil - } case coredata.ReportEntityType: action = probo.ActionReportGet loadNode = func(ctx context.Context, id gid.GID) (types.Node, error) { @@ -9266,16 +9251,6 @@ func (r *Resolver) ComplianceFramework() schema.ComplianceFrameworkResolver { return &complianceFrameworkResolver{r} } -// ContinualImprovement returns schema.ContinualImprovementResolver implementation. -func (r *Resolver) ContinualImprovement() schema.ContinualImprovementResolver { - return &continualImprovementResolver{r} -} - -// ContinualImprovementConnection returns schema.ContinualImprovementConnectionResolver implementation. -func (r *Resolver) ContinualImprovementConnection() schema.ContinualImprovementConnectionResolver { - return &continualImprovementConnectionResolver{r} -} - // Control returns schema.ControlResolver implementation. func (r *Resolver) Control() schema.ControlResolver { return &controlResolver{r} } @@ -9349,6 +9324,14 @@ func (r *Resolver) EvidenceConnection() schema.EvidenceConnectionResolver { // File returns schema.FileResolver implementation. func (r *Resolver) File() schema.FileResolver { return &fileResolver{r} } +// Finding returns schema.FindingResolver implementation. +func (r *Resolver) Finding() schema.FindingResolver { return &findingResolver{r} } + +// FindingConnection returns schema.FindingConnectionResolver implementation. +func (r *Resolver) FindingConnection() schema.FindingConnectionResolver { + return &findingConnectionResolver{r} +} + // Framework returns schema.FrameworkResolver implementation. func (r *Resolver) Framework() schema.FrameworkResolver { return &frameworkResolver{r} } @@ -9389,14 +9372,6 @@ func (r *Resolver) MeetingConnection() schema.MeetingConnectionResolver { // Mutation returns schema.MutationResolver implementation. func (r *Resolver) Mutation() schema.MutationResolver { return &mutationResolver{r} } -// Nonconformity returns schema.NonconformityResolver implementation. -func (r *Resolver) Nonconformity() schema.NonconformityResolver { return &nonconformityResolver{r} } - -// NonconformityConnection returns schema.NonconformityConnectionResolver implementation. -func (r *Resolver) NonconformityConnection() schema.NonconformityConnectionResolver { - return &nonconformityConnectionResolver{r} -} - // Obligation returns schema.ObligationResolver implementation. func (r *Resolver) Obligation() schema.ObligationResolver { return &obligationResolver{r} } @@ -9583,8 +9558,6 @@ type auditResolver struct{ *Resolver } type auditConnectionResolver struct{ *Resolver } type complianceExternalURLResolver struct{ *Resolver } type complianceFrameworkResolver struct{ *Resolver } -type continualImprovementResolver struct{ *Resolver } -type continualImprovementConnectionResolver struct{ *Resolver } type controlResolver struct{ *Resolver } type controlConnectionResolver struct{ *Resolver } type customDomainResolver struct{ *Resolver } @@ -9602,6 +9575,8 @@ type electronicSignatureResolver struct{ *Resolver } type evidenceResolver struct{ *Resolver } type evidenceConnectionResolver struct{ *Resolver } type fileResolver struct{ *Resolver } +type findingResolver struct{ *Resolver } +type findingConnectionResolver struct{ *Resolver } type frameworkResolver struct{ *Resolver } type frameworkConnectionResolver struct{ *Resolver } type mailingListResolver struct{ *Resolver } @@ -9612,8 +9587,6 @@ type measureConnectionResolver struct{ *Resolver } type meetingResolver struct{ *Resolver } type meetingConnectionResolver struct{ *Resolver } type mutationResolver struct{ *Resolver } -type nonconformityResolver struct{ *Resolver } -type nonconformityConnectionResolver struct{ *Resolver } type obligationResolver struct{ *Resolver } type obligationConnectionResolver struct{ *Resolver } type organizationResolver struct{ *Resolver }