diff --git a/apps/console/src/pages/organizations/vendors/VendorView.tsx b/apps/console/src/pages/organizations/vendors/VendorView.tsx index 3b404f791..3524ec6d9 100644 --- a/apps/console/src/pages/organizations/vendors/VendorView.tsx +++ b/apps/console/src/pages/organizations/vendors/VendorView.tsx @@ -94,6 +94,13 @@ const vendorViewQuery = graphql` organization: node(id: $organizationId) { ...PeopleSelector_organization } + viewer { + user { + people(organizationId: $organizationId) { + id + } + } + } } `; @@ -675,6 +682,13 @@ function RiskAssessmentsTable({ }); }; + // Check if assessment is expired + const isExpired = (expiresAt: string) => { + console.log(new Date(expiresAt) < new Date()); + + return new Date(expiresAt) < new Date(); + }; + // Get severity label based on data sensitivity and business impact const getSeverityLabel = (dataSensitivity: string, businessImpact: string) => { // Simple logic to determine severity - can be adjusted based on requirements @@ -731,35 +745,67 @@ function RiskAssessmentsTable({ {assessments.map((assessment) => { const severity = getSeverityLabel(assessment.dataSensitivity, assessment.businessImpact); + const expired = isExpired(assessment.expiresAt); return ( - + - + {formatDate(assessment.assessedAt)} - - {formatDate(assessment.expiresAt)} - +
+ + {formatDate(assessment.expiresAt)} + + {expired && ( + + Expired + + )} +
- + {assessment.dataSensitivity.charAt(0) + assessment.dataSensitivity.slice(1).toLowerCase()} - + {assessment.businessImpact.charAt(0) + assessment.businessImpact.slice(1).toLowerCase()} - + {severity.label} - + {assessment.assessedBy?.fullName || 'N/A'} @@ -770,9 +816,9 @@ function RiskAssessmentsTable({ onClick={() => setShowDropdown(showDropdown === assessment.id ? null : assessment.id)} > - - - + + + {showDropdown === assessment.id && ( @@ -1270,27 +1316,9 @@ function VendorViewContent({ businessImpact: BusinessImpact; notes: string; }) => { - // Close the modal - setShowRiskAssessmentModal(false); - - // Current date for assessedAt, and 1 year later for expiresAt - const today = new Date(); - const nextYear = new Date(today); + const nextYear = new Date(); nextYear.setFullYear(nextYear.getFullYear() + 1); - // Check if there's a business owner assigned - const businessOwnerId = data.node.businessOwner?.id; - if (!businessOwnerId) { - // This check is now redundant as it's handled in the modal - // but keeping it as a safeguard - toast({ - title: "Error", - description: "Please assign a Business Owner to the vendor before creating a risk assessment", - variant: "destructive", - }); - return; - } - createRiskAssessment({ variables: { connections: [ @@ -1301,12 +1329,11 @@ function VendorViewContent({ ], input: { vendorId: data.node.id!, - assessedBy: businessOwnerId, + assessedBy: data.viewer.user.people?.id!, expiresAt: nextYear.toISOString(), dataSensitivity: formValues.dataSensitivity, businessImpact: formValues.businessImpact, notes: formValues.notes || "Risk assessment", - attachments: [] }, }, onCompleted: () => { @@ -1315,6 +1342,7 @@ function VendorViewContent({ description: "Risk assessment created successfully", variant: "default", }); + setShowRiskAssessmentModal(false); loadQuery({ vendorId: data.node.id!, organizationId: organizationId!, @@ -1328,7 +1356,7 @@ function VendorViewContent({ }); }, }); - }, [createRiskAssessment, data.node.id, data.node.businessOwner?.id, loadQuery, toast, organizationId]); + }, [createRiskAssessment, data.node.id, data.viewer.user.people?.id, loadQuery, toast, organizationId]); const formatDate = (dateStr: string | null | undefined) => { if (!dateStr) return "N/A"; diff --git a/apps/console/src/pages/organizations/vendors/__generated__/VendorViewCreateRiskAssessmentMutation.graphql.ts b/apps/console/src/pages/organizations/vendors/__generated__/VendorViewCreateRiskAssessmentMutation.graphql.ts index 0dbc113ad..66d14c5b1 100644 --- a/apps/console/src/pages/organizations/vendors/__generated__/VendorViewCreateRiskAssessmentMutation.graphql.ts +++ b/apps/console/src/pages/organizations/vendors/__generated__/VendorViewCreateRiskAssessmentMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<8372d50934049d43afb678c894f40dbd>> + * @generated SignedSource<<08de2d8758474132b472d63f413b59a5>> * @lightSyntaxTransform * @nogrep */ @@ -13,7 +13,6 @@ export type BusinessImpact = "CRITICAL" | "HIGH" | "LOW" | "MEDIUM"; export type DataSensitivity = "CRITICAL" | "HIGH" | "LOW" | "MEDIUM" | "NONE"; export type CreateVendorRiskAssessmentInput = { assessedBy: string; - attachments?: ReadonlyArray | null | undefined; businessImpact: BusinessImpact; dataSensitivity: DataSensitivity; expiresAt: string; diff --git a/apps/console/src/pages/organizations/vendors/__generated__/VendorViewQuery.graphql.ts b/apps/console/src/pages/organizations/vendors/__generated__/VendorViewQuery.graphql.ts index d9e08ccc1..089ebee3f 100644 --- a/apps/console/src/pages/organizations/vendors/__generated__/VendorViewQuery.graphql.ts +++ b/apps/console/src/pages/organizations/vendors/__generated__/VendorViewQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -78,6 +78,13 @@ export type VendorViewQuery$data = { readonly organization: { readonly " $fragmentSpreads": FragmentRefs<"PeopleSelector_organization">; }; + readonly viewer: { + readonly user: { + readonly people: { + readonly id: string; + } | null | undefined; + }; + }; }; export type VendorViewQuery = { response: VendorViewQuery$data; @@ -444,15 +451,33 @@ v30 = [ } ], v31 = { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "organizationId", + "variableName": "organizationId" + } + ], + "concreteType": "People", + "kind": "LinkedField", + "name": "people", + "plural": false, + "selections": [ + (v3/*: any*/) + ], + "storageKey": null +}, +v32 = { "kind": "Literal", "name": "first", "value": 100 }, -v32 = [ - (v31/*: any*/) -], v33 = [ - (v31/*: any*/), + (v32/*: any*/) +], +v34 = [ + (v32/*: any*/), { "kind": "Literal", "name": "orderBy", @@ -545,6 +570,29 @@ return { } ], "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Viewer", + "kind": "LinkedField", + "name": "viewer", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "user", + "plural": false, + "selections": [ + (v31/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null } ], "type": "Query", @@ -593,7 +641,7 @@ return { (v24/*: any*/), { "alias": null, - "args": (v32/*: any*/), + "args": (v33/*: any*/), "concreteType": "VendorComplianceReportConnection", "kind": "LinkedField", "name": "complianceReports", @@ -603,7 +651,7 @@ return { }, { "alias": null, - "args": (v32/*: any*/), + "args": (v33/*: any*/), "filters": null, "handle": "connection", "key": "VendorView_complianceReports", @@ -612,7 +660,7 @@ return { }, { "alias": null, - "args": (v32/*: any*/), + "args": (v33/*: any*/), "concreteType": "VendorRiskAssessmentConnection", "kind": "LinkedField", "name": "riskAssessments", @@ -622,7 +670,7 @@ return { }, { "alias": null, - "args": (v32/*: any*/), + "args": (v33/*: any*/), "filters": null, "handle": "connection", "key": "VendorView_riskAssessments", @@ -651,7 +699,7 @@ return { "selections": [ { "alias": null, - "args": (v33/*: any*/), + "args": (v34/*: any*/), "concreteType": "PeopleConnection", "kind": "LinkedField", "name": "peoples", @@ -696,7 +744,7 @@ return { }, { "alias": null, - "args": (v33/*: any*/), + "args": (v34/*: any*/), "filters": [ "orderBy" ], @@ -711,11 +759,36 @@ return { } ], "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Viewer", + "kind": "LinkedField", + "name": "viewer", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "User", + "kind": "LinkedField", + "name": "user", + "plural": false, + "selections": [ + (v31/*: any*/), + (v3/*: any*/) + ], + "storageKey": null + }, + (v3/*: any*/) + ], + "storageKey": null } ] }, "params": { - "cacheID": "5df12f781d305bd4bd6b6c0ccb4d947e", + "cacheID": "9b2a625a96210e0168dceed7e4b98ed0", "id": null, "metadata": { "connection": [ @@ -741,11 +814,11 @@ return { }, "name": "VendorViewQuery", "operationKind": "query", - "text": "query VendorViewQuery(\n $vendorId: ID!\n $organizationId: ID!\n) {\n node(id: $vendorId) {\n __typename\n ... on Vendor {\n id\n name\n description\n serviceStartAt\n serviceTerminationAt\n statusPageUrl\n termsOfServiceUrl\n privacyPolicyUrl\n serviceLevelAgreementUrl\n dataProcessingAgreementUrl\n securityPageUrl\n trustPageUrl\n certifications\n headquarterAddress\n legalName\n websiteUrl\n businessOwner {\n id\n fullName\n }\n securityOwner {\n id\n fullName\n }\n createdAt\n updatedAt\n complianceReports(first: 100) {\n edges {\n node {\n id\n reportName\n reportDate\n validUntil\n fileUrl\n fileSize\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n riskAssessments(first: 100) {\n edges {\n node {\n id\n assessedAt\n expiresAt\n dataSensitivity\n businessImpact\n notes\n assessedBy {\n id\n fullName\n }\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n organization: node(id: $organizationId) {\n __typename\n ...PeopleSelector_organization\n id\n }\n}\n\nfragment PeopleSelector_organization on Organization {\n id\n peoples(first: 100, orderBy: {direction: ASC, field: FULL_NAME}) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n" + "text": "query VendorViewQuery(\n $vendorId: ID!\n $organizationId: ID!\n) {\n node(id: $vendorId) {\n __typename\n ... on Vendor {\n id\n name\n description\n serviceStartAt\n serviceTerminationAt\n statusPageUrl\n termsOfServiceUrl\n privacyPolicyUrl\n serviceLevelAgreementUrl\n dataProcessingAgreementUrl\n securityPageUrl\n trustPageUrl\n certifications\n headquarterAddress\n legalName\n websiteUrl\n businessOwner {\n id\n fullName\n }\n securityOwner {\n id\n fullName\n }\n createdAt\n updatedAt\n complianceReports(first: 100) {\n edges {\n node {\n id\n reportName\n reportDate\n validUntil\n fileUrl\n fileSize\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n riskAssessments(first: 100) {\n edges {\n node {\n id\n assessedAt\n expiresAt\n dataSensitivity\n businessImpact\n notes\n assessedBy {\n id\n fullName\n }\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n organization: node(id: $organizationId) {\n __typename\n ...PeopleSelector_organization\n id\n }\n viewer {\n user {\n people(organizationId: $organizationId) {\n id\n }\n id\n }\n id\n }\n}\n\nfragment PeopleSelector_organization on Organization {\n id\n peoples(first: 100, orderBy: {direction: ASC, field: FULL_NAME}) {\n edges {\n node {\n id\n fullName\n primaryEmailAddress\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n" } }; })(); -(node as any).hash = "2b856ea721d1bfb6b853fc281ee1aac6"; +(node as any).hash = "7f9a178bd20cefd76440ea8d71c3b2fa"; export default node; diff --git a/pkg/coredata/migrations/20250422T033400Z.sql b/pkg/coredata/migrations/20250422T033400Z.sql new file mode 100644 index 000000000..944e599a4 --- /dev/null +++ b/pkg/coredata/migrations/20250422T033400Z.sql @@ -0,0 +1,2 @@ +ALTER TABLE vendor_risk_assessments ALTER COLUMN approved_at DROP NOT NULL; +ALTER TABLE vendor_risk_assessments ALTER COLUMN approved_by DROP NOT NULL; diff --git a/pkg/coredata/migrations/20250422T152800Z.sql b/pkg/coredata/migrations/20250422T152800Z.sql new file mode 100644 index 000000000..8a883baf2 --- /dev/null +++ b/pkg/coredata/migrations/20250422T152800Z.sql @@ -0,0 +1,3 @@ + +ALTER TABLE vendor_risk_assessments DROP COLUMN accessed_at; +ALTER TABLE vendor_risk_assessments DROP COLUMN accessed_by; diff --git a/pkg/coredata/people.go b/pkg/coredata/people.go index 9cd913e55..0c42cc907 100644 --- a/pkg/coredata/people.go +++ b/pkg/coredata/people.go @@ -98,6 +98,53 @@ LIMIT 1; return nil } +func (p *People) LoadByUserID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + organizationID gid.GID, + userID gid.GID, +) error { + q := ` +SELECT + id, + organization_id, + kind, + user_id, + full_name, + primary_email_address, + additional_email_addresses, + created_at, + updated_at +FROM + peoples +WHERE + %s + AND organization_id = @organization_id + AND user_id = @user_id +LIMIT 1; +` + + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{"organization_id": organizationID, "user_id": userID} + maps.Copy(args, scope.SQLArguments()) + + rows, err := conn.Query(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot query people: %w", err) + } + + people, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[People]) + if err != nil { + return fmt.Errorf("cannot collect people: %w", err) + } + + *p = people + + return nil +} + func (p People) Insert( ctx context.Context, conn pg.Conn, diff --git a/pkg/coredata/vendor.go b/pkg/coredata/vendor.go index 96ce189df..6349301ef 100644 --- a/pkg/coredata/vendor.go +++ b/pkg/coredata/vendor.go @@ -350,3 +350,37 @@ WHERE %s _, err := conn.Exec(ctx, q, args) return err } + +func (v Vendor) ExpireNonExpiredRiskAssessments( + ctx context.Context, + conn pg.Conn, + scope Scoper, +) error { + now := time.Now() + + q := ` + UPDATE vendor_risk_assessments + SET + expires_at = @now, + updated_at = @now + WHERE + %s + AND vendor_id = @vendor_id + AND expires_at > @now + ` + + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{ + "vendor_id": v.ID, + "now": now, + } + maps.Copy(args, scope.SQLArguments()) + + _, err := conn.Exec(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot expire existing risk assessments: %w", err) + } + + return nil +} diff --git a/pkg/coredata/vendor_risk_assessment.go b/pkg/coredata/vendor_risk_assessment.go index c4319eb9d..3333439f8 100644 --- a/pkg/coredata/vendor_risk_assessment.go +++ b/pkg/coredata/vendor_risk_assessment.go @@ -33,9 +33,8 @@ type ( VendorID gid.GID `db:"vendor_id"` AssessedAt time.Time `db:"assessed_at"` AssessedBy gid.GID `db:"assessed_by"` - AccessedAt time.Time `db:"accessed_at"` - ApprovedBy gid.GID `db:"approved_by"` - ApprovedAt time.Time `db:"approved_at"` + ApprovedBy *gid.GID `db:"approved_by"` + ApprovedAt *time.Time `db:"approved_at"` ExpiresAt time.Time `db:"expires_at"` DataSensitivity DataSensitivity `db:"data_sensitivity"` BusinessImpact BusinessImpact `db:"business_impact"` @@ -96,7 +95,6 @@ INSERT INTO vendor_id, assessed_at, assessed_by, - accessed_at, approved_by, approved_at, expires_at, @@ -112,11 +110,12 @@ VALUES ( @vendor_id, @assessed_at, @assessed_by, + @approved_by, + @approved_at, @expires_at, @data_sensitivity, @business_impact, @notes, - @attachments, @created_at, @updated_at ) @@ -128,7 +127,6 @@ VALUES ( "vendor_id": r.VendorID, "assessed_at": r.AssessedAt, "assessed_by": r.AssessedBy, - "accessed_at": r.AccessedAt, "approved_by": r.ApprovedBy, "approved_at": r.ApprovedAt, "expires_at": r.ExpiresAt, @@ -155,7 +153,6 @@ SELECT vendor_id, assessed_at, assessed_by, - accessed_at, approved_by, approved_at, expires_at, @@ -206,7 +203,6 @@ SELECT vendor_id, assessed_at, assessed_by, - accessed_at, approved_by, approved_at, expires_at, @@ -260,7 +256,6 @@ SELECT vendor_id, assessed_at, assessed_by, - accessed_at, approved_by, approved_at, expires_at, diff --git a/pkg/probo/people_service.go b/pkg/probo/people_service.go index cafb5d767..c9b8ad6fa 100644 --- a/pkg/probo/people_service.go +++ b/pkg/probo/people_service.go @@ -69,6 +69,27 @@ func (s PeopleService) Get( return people, nil } +func (s PeopleService) GetByUserID( + ctx context.Context, + organizationID gid.GID, + userID gid.GID, +) (*coredata.People, error) { + people := &coredata.People{} + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) error { + return people.LoadByUserID(ctx, conn, s.svc.scope, organizationID, userID) + }, + ) + + if err != nil { + return nil, err + } + + return people, nil +} + func (s PeopleService) ListForOrganizationID( ctx context.Context, organizationID gid.GID, diff --git a/pkg/probo/vendor_service.go b/pkg/probo/vendor_service.go index 73c8f24fe..7e5a132e3 100644 --- a/pkg/probo/vendor_service.go +++ b/pkg/probo/vendor_service.go @@ -359,6 +359,7 @@ func (s VendorService) CreateRiskAssessment( ID: vendorRiskAssessmentID, VendorID: req.VendorID, AssessedBy: req.AssessedByID, + AssessedAt: now, ExpiresAt: req.ExpiresAt, DataSensitivity: req.DataSensitivity, BusinessImpact: req.BusinessImpact, @@ -369,11 +370,15 @@ func (s VendorService) CreateRiskAssessment( err = s.svc.pg.WithTx( ctx, - func(conn pg.Conn) error { - if err := vendorRiskAssessment.Insert(ctx, conn, s.svc.scope); err != nil { - return fmt.Errorf("cannot insert vendor risk assessment: %w", err) + func(tx pg.Conn) error { + vendor := coredata.Vendor{ID: req.VendorID} + if err := vendor.ExpireNonExpiredRiskAssessments(ctx, tx, s.svc.scope); err != nil { + return fmt.Errorf("cannot expire vendor risk assessments: %w", err) } + if err := vendorRiskAssessment.Insert(ctx, tx, s.svc.scope); err != nil { + return fmt.Errorf("cannot insert vendor risk assessment: %w", err) + } return nil }, ) @@ -384,3 +389,23 @@ func (s VendorService) CreateRiskAssessment( return vendorRiskAssessment, nil } + +func (s VendorService) GetRiskAssessment( + ctx context.Context, + vendorRiskAssessmentID gid.GID, +) (*coredata.VendorRiskAssessment, error) { + vendorRiskAssessment := &coredata.VendorRiskAssessment{} + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) error { + return vendorRiskAssessment.LoadByID(ctx, conn, s.svc.scope, vendorRiskAssessmentID) + }, + ) + + if err != nil { + return nil, err + } + + return vendorRiskAssessment, nil +} diff --git a/pkg/server/api/console/v1/schema.graphql b/pkg/server/api/console/v1/schema.graphql index d7e00c6c3..2f9c5df69 100644 --- a/pkg/server/api/console/v1/schema.graphql +++ b/pkg/server/api/console/v1/schema.graphql @@ -455,6 +455,8 @@ type User implements Node { email: String! createdAt: Datetime! updatedAt: Datetime! + + people(organizationId: ID!): People @goField(forceResolver: true) } type Connector implements Node { @@ -1463,7 +1465,6 @@ type VendorRiskAssessment implements Node { dataSensitivity: DataSensitivity! businessImpact: BusinessImpact! notes: String - attachments: [String!]! createdAt: Datetime! updatedAt: Datetime! } @@ -1487,7 +1488,6 @@ input CreateVendorRiskAssessmentInput { dataSensitivity: DataSensitivity! businessImpact: BusinessImpact! notes: String - attachments: [String!] } type CreateVendorRiskAssessmentPayload { diff --git a/pkg/server/api/console/v1/schema/schema.go b/pkg/server/api/console/v1/schema/schema.go index 7ed04375a..c8e769ed5 100644 --- a/pkg/server/api/console/v1/schema/schema.go +++ b/pkg/server/api/console/v1/schema/schema.go @@ -52,6 +52,7 @@ type ResolverRoot interface { Query() QueryResolver Risk() RiskResolver Task() TaskResolver + User() UserResolver Vendor() VendorResolver VendorComplianceReport() VendorComplianceReportResolver VendorRiskAssessment() VendorRiskAssessmentResolver @@ -538,6 +539,7 @@ type ComplexityRoot struct { Email func(childComplexity int) int FullName func(childComplexity int) int ID func(childComplexity int) int + People func(childComplexity int, organizationID gid.GID) int UpdatedAt func(childComplexity int) int } @@ -611,7 +613,6 @@ type ComplexityRoot struct { VendorRiskAssessment struct { AssessedAt func(childComplexity int) int AssessedBy func(childComplexity int) int - Attachments func(childComplexity int) int BusinessImpact func(childComplexity int) int CreatedAt func(childComplexity int) int DataSensitivity func(childComplexity int) int @@ -730,6 +731,9 @@ type TaskResolver interface { AssignedTo(ctx context.Context, obj *types.Task) (*types.People, error) Evidences(ctx context.Context, obj *types.Task, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.EvidenceOrderBy) (*types.EvidenceConnection, error) } +type UserResolver interface { + People(ctx context.Context, obj *types.User, organizationID gid.GID) (*types.People, error) +} type VendorResolver interface { ComplianceReports(ctx context.Context, obj *types.Vendor, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorComplianceReportOrderBy) (*types.VendorComplianceReportConnection, error) RiskAssessments(ctx context.Context, obj *types.Vendor, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.VendorRiskAssessmentOrder) (*types.VendorRiskAssessmentConnection, error) @@ -2760,6 +2764,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.User.ID(childComplexity), true + case "User.people": + if e.complexity.User.People == nil { + break + } + + args, err := ec.field_User_people_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.User.People(childComplexity, args["organizationId"].(gid.GID)), true + case "User.updatedAt": if e.complexity.User.UpdatedAt == nil { break @@ -3092,13 +3108,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.VendorRiskAssessment.AssessedBy(childComplexity), true - case "VendorRiskAssessment.attachments": - if e.complexity.VendorRiskAssessment.Attachments == nil { - break - } - - return e.complexity.VendorRiskAssessment.Attachments(childComplexity), true - case "VendorRiskAssessment.businessImpact": if e.complexity.VendorRiskAssessment.BusinessImpact == nil { break @@ -3830,6 +3839,8 @@ type User implements Node { email: String! createdAt: Datetime! updatedAt: Datetime! + + people(organizationId: ID!): People @goField(forceResolver: true) } type Connector implements Node { @@ -4838,7 +4849,6 @@ type VendorRiskAssessment implements Node { dataSensitivity: DataSensitivity! businessImpact: BusinessImpact! notes: String - attachments: [String!]! createdAt: Datetime! updatedAt: Datetime! } @@ -4862,7 +4872,6 @@ input CreateVendorRiskAssessmentInput { dataSensitivity: DataSensitivity! businessImpact: BusinessImpact! notes: String - attachments: [String!] } type CreateVendorRiskAssessmentPayload { @@ -7762,6 +7771,29 @@ func (ec *executionContext) field_Task_evidences_argsOrderBy( return zeroVal, nil } +func (ec *executionContext) field_User_people_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_User_people_argsOrganizationID(ctx, rawArgs) + if err != nil { + return nil, err + } + args["organizationId"] = arg0 + return args, nil +} +func (ec *executionContext) field_User_people_argsOrganizationID( + ctx context.Context, + rawArgs map[string]any, +) (gid.GID, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("organizationId")) + if tmp, ok := rawArgs["organizationId"]; ok { + return ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, tmp) + } + + var zeroVal gid.GID + return zeroVal, nil +} + func (ec *executionContext) field_Vendor_complianceReports_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -20538,6 +20570,74 @@ func (ec *executionContext) fieldContext_User_updatedAt(_ context.Context, field return fc, nil } +func (ec *executionContext) _User_people(ctx context.Context, field graphql.CollectedField, obj *types.User) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_User_people(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.User().People(rctx, obj, fc.Args["organizationId"].(gid.GID)) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*types.People) + fc.Result = res + return ec.marshalOPeople2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐPeople(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_User_people(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "User", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_People_id(ctx, field) + case "fullName": + return ec.fieldContext_People_fullName(ctx, field) + case "primaryEmailAddress": + return ec.fieldContext_People_primaryEmailAddress(ctx, field) + case "additionalEmailAddresses": + return ec.fieldContext_People_additionalEmailAddresses(ctx, field) + case "kind": + return ec.fieldContext_People_kind(ctx, field) + case "createdAt": + return ec.fieldContext_People_createdAt(ctx, field) + case "updatedAt": + return ec.fieldContext_People_updatedAt(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type People", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_User_people_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _UserConnection_edges(ctx context.Context, field graphql.CollectedField, obj *types.UserConnection) (ret graphql.Marshaler) { fc, err := ec.fieldContext_UserConnection_edges(ctx, field) if err != nil { @@ -20735,6 +20835,8 @@ func (ec *executionContext) fieldContext_UserEdge_node(_ context.Context, field return ec.fieldContext_User_createdAt(ctx, field) case "updatedAt": return ec.fieldContext_User_updatedAt(ctx, field) + case "people": + return ec.fieldContext_User_people(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) }, @@ -23034,50 +23136,6 @@ func (ec *executionContext) fieldContext_VendorRiskAssessment_notes(_ context.Co return fc, nil } -func (ec *executionContext) _VendorRiskAssessment_attachments(ctx context.Context, field graphql.CollectedField, obj *types.VendorRiskAssessment) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_VendorRiskAssessment_attachments(ctx, field) - if err != nil { - return graphql.Null - } - ctx = graphql.WithFieldContext(ctx, fc) - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = graphql.Null - } - }() - resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { - ctx = rctx // use context from middleware stack in children - return obj.Attachments, nil - }) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - if resTmp == nil { - if !graphql.HasFieldError(ctx, fc) { - ec.Errorf(ctx, "must not be null") - } - return graphql.Null - } - res := resTmp.([]string) - fc.Result = res - return ec.marshalNString2ᚕstringᚄ(ctx, field.Selections, res) -} - -func (ec *executionContext) fieldContext_VendorRiskAssessment_attachments(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { - fc = &graphql.FieldContext{ - Object: "VendorRiskAssessment", - Field: field, - IsMethod: false, - IsResolver: false, - Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { - return nil, errors.New("field of type String does not have child fields") - }, - } - return fc, nil -} - func (ec *executionContext) _VendorRiskAssessment_createdAt(ctx context.Context, field graphql.CollectedField, obj *types.VendorRiskAssessment) (ret graphql.Marshaler) { fc, err := ec.fieldContext_VendorRiskAssessment_createdAt(ctx, field) if err != nil { @@ -23369,8 +23427,6 @@ func (ec *executionContext) fieldContext_VendorRiskAssessmentEdge_node(_ context return ec.fieldContext_VendorRiskAssessment_businessImpact(ctx, field) case "notes": return ec.fieldContext_VendorRiskAssessment_notes(ctx, field) - case "attachments": - return ec.fieldContext_VendorRiskAssessment_attachments(ctx, field) case "createdAt": return ec.fieldContext_VendorRiskAssessment_createdAt(ctx, field) case "updatedAt": @@ -23475,6 +23531,8 @@ func (ec *executionContext) fieldContext_Viewer_user(_ context.Context, field gr return ec.fieldContext_User_createdAt(ctx, field) case "updatedAt": return ec.fieldContext_User_updatedAt(ctx, field) + case "people": + return ec.fieldContext_User_people(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type User", field.Name) }, @@ -26366,7 +26424,7 @@ func (ec *executionContext) unmarshalInputCreateVendorRiskAssessmentInput(ctx co asMap[k] = v } - fieldsInOrder := [...]string{"vendorId", "assessedBy", "expiresAt", "dataSensitivity", "businessImpact", "notes", "attachments"} + fieldsInOrder := [...]string{"vendorId", "assessedBy", "expiresAt", "dataSensitivity", "businessImpact", "notes"} for _, k := range fieldsInOrder { v, ok := asMap[k] if !ok { @@ -26415,13 +26473,6 @@ func (ec *executionContext) unmarshalInputCreateVendorRiskAssessmentInput(ctx co return it, err } it.Notes = data - case "attachments": - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("attachments")) - data, err := ec.unmarshalOString2ᚕstringᚄ(ctx, v) - if err != nil { - return it, err - } - it.Attachments = data } } @@ -32945,28 +32996,61 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj case "id": out.Values[i] = ec._User_id(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "fullName": out.Values[i] = ec._User_fullName(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "email": out.Values[i] = ec._User_email(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "createdAt": out.Values[i] = ec._User_createdAt(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } case "updatedAt": out.Values[i] = ec._User_updatedAt(ctx, field, obj) if out.Values[i] == graphql.Null { - out.Invalids++ + atomic.AddUint32(&out.Invalids, 1) } + case "people": + field := field + + innerFunc := func(ctx context.Context, _ *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._User_people(ctx, field, obj) + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -33728,11 +33812,6 @@ func (ec *executionContext) _VendorRiskAssessment(ctx context.Context, sel ast.S } case "notes": out.Values[i] = ec._VendorRiskAssessment_notes(ctx, field, obj) - case "attachments": - out.Values[i] = ec._VendorRiskAssessment_attachments(ctx, field, obj) - if out.Values[i] == graphql.Null { - atomic.AddUint32(&out.Invalids, 1) - } case "createdAt": out.Values[i] = ec._VendorRiskAssessment_createdAt(ctx, field, obj) if out.Values[i] == graphql.Null { diff --git a/pkg/server/api/console/v1/types/types.go b/pkg/server/api/console/v1/types/types.go index 9f93fd0d1..1b4f55dba 100644 --- a/pkg/server/api/console/v1/types/types.go +++ b/pkg/server/api/console/v1/types/types.go @@ -252,7 +252,6 @@ type CreateVendorRiskAssessmentInput struct { DataSensitivity coredata.DataSensitivity `json:"dataSensitivity"` BusinessImpact coredata.BusinessImpact `json:"businessImpact"` Notes *string `json:"notes,omitempty"` - Attachments []string `json:"attachments,omitempty"` } type CreateVendorRiskAssessmentPayload struct { @@ -799,6 +798,7 @@ type User struct { Email string `json:"email"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` + People *People `json:"people,omitempty"` } func (User) IsNode() {} @@ -886,7 +886,6 @@ type VendorRiskAssessment struct { DataSensitivity coredata.DataSensitivity `json:"dataSensitivity"` BusinessImpact coredata.BusinessImpact `json:"businessImpact"` Notes *string `json:"notes,omitempty"` - Attachments []string `json:"attachments"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go index 566368c80..30809947a 100644 --- a/pkg/server/api/console/v1/v1_resolver.go +++ b/pkg/server/api/console/v1/v1_resolver.go @@ -1049,16 +1049,19 @@ func (r *mutationResolver) DeletePolicy(ctx context.Context, input types.DeleteP // CreateVendorRiskAssessment is the resolver for the createVendorRiskAssessment field. func (r *mutationResolver) CreateVendorRiskAssessment(ctx context.Context, input types.CreateVendorRiskAssessmentInput) (*types.CreateVendorRiskAssessmentPayload, error) { svc := GetTenantService(ctx, r.proboSvc, input.VendorID.TenantID()) - user := UserFromContext(ctx) - expiresAt := time.Now().Add(time.Hour * 24 * 365) + fmt.Println("input.AssessedBy", input.AssessedBy) + fmt.Println("input.ExpiresAt", input.ExpiresAt) + fmt.Println("input.DataSensitivity", input.DataSensitivity) + fmt.Println("input.BusinessImpact", input.BusinessImpact) + fmt.Println("input.Notes", input.Notes) vendorRiskAssessment, err := svc.Vendors.CreateRiskAssessment( ctx, probo.CreateVendorRiskAssessmentRequest{ VendorID: input.VendorID, - AssessedByID: user.ID, - ExpiresAt: expiresAt, + AssessedByID: input.AssessedBy, + ExpiresAt: input.ExpiresAt, DataSensitivity: input.DataSensitivity, BusinessImpact: input.BusinessImpact, Notes: input.Notes, @@ -1558,6 +1561,18 @@ func (r *taskResolver) Evidences(ctx context.Context, obj *types.Task, first *in return types.NewEvidenceConnection(page), nil } +// People is the resolver for the people field. +func (r *userResolver) People(ctx context.Context, obj *types.User, organizationID gid.GID) (*types.People, error) { + svc := GetTenantService(ctx, r.proboSvc, organizationID.TenantID()) + + people, err := svc.Peoples.GetByUserID(ctx, organizationID, obj.ID) + if err != nil { + panic(fmt.Errorf("failed to get people: %w", err)) + } + + return types.NewPeople(people), 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()) @@ -1676,12 +1691,31 @@ func (r *vendorComplianceReportResolver) FileURL(ctx context.Context, obj *types // Vendor is the resolver for the vendor field. func (r *vendorRiskAssessmentResolver) Vendor(ctx context.Context, obj *types.VendorRiskAssessment) (*types.Vendor, error) { - panic(fmt.Errorf("not implemented: Vendor - vendor")) + svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID()) + + vendor, err := svc.Vendors.Get(ctx, obj.ID) + if err != nil { + panic(fmt.Errorf("failed to get vendor: %w", err)) + } + + return types.NewVendor(vendor), nil } // AssessedBy is the resolver for the assessedBy field. func (r *vendorRiskAssessmentResolver) AssessedBy(ctx context.Context, obj *types.VendorRiskAssessment) (*types.People, error) { - panic(fmt.Errorf("not implemented: AssessedBy - assessedBy")) + svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID()) + + vendorRiskAssessment, err := svc.Vendors.GetRiskAssessment(ctx, obj.ID) + if err != nil { + panic(fmt.Errorf("failed to get vendor risk assessment: %w", err)) + } + + people, err := svc.Peoples.Get(ctx, vendorRiskAssessment.AssessedBy) + if err != nil { + panic(fmt.Errorf("failed to get assessed by: %w", err)) + } + + return types.NewPeople(people), nil } // Organizations is the resolver for the organizations field. @@ -1739,6 +1773,9 @@ func (r *Resolver) Risk() schema.RiskResolver { return &riskResolver{r} } // Task returns schema.TaskResolver implementation. func (r *Resolver) Task() schema.TaskResolver { return &taskResolver{r} } +// User returns schema.UserResolver implementation. +func (r *Resolver) User() schema.UserResolver { return &userResolver{r} } + // Vendor returns schema.VendorResolver implementation. func (r *Resolver) Vendor() schema.VendorResolver { return &vendorResolver{r} } @@ -1765,6 +1802,7 @@ type policyResolver struct{ *Resolver } type queryResolver struct{ *Resolver } type riskResolver struct{ *Resolver } type taskResolver struct{ *Resolver } +type userResolver struct{ *Resolver } type vendorResolver struct{ *Resolver } type vendorComplianceReportResolver struct{ *Resolver } type vendorRiskAssessmentResolver struct{ *Resolver }