diff --git a/apps/console/src/pages/organizations/vendors/ListVendorView.tsx b/apps/console/src/pages/organizations/vendors/ListVendorView.tsx index d795b95a4..b010fc597 100644 --- a/apps/console/src/pages/organizations/vendors/ListVendorView.tsx +++ b/apps/console/src/pages/organizations/vendors/ListVendorView.tsx @@ -23,6 +23,12 @@ import { ListVendorViewPaginationQuery } from "./__generated__/ListVendorViewPag import { ListVendorView_vendors$key } from "./__generated__/ListVendorView_vendors.graphql"; import { ListVendorViewQuery } from "./__generated__/ListVendorViewQuery.graphql"; import { ListVendorViewDeleteVendorMutation } from "./__generated__/ListVendorViewDeleteVendorMutation.graphql"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; interface VendorData { name: string; @@ -86,6 +92,17 @@ const vendorListFragment = graphql` websiteUrl createdAt updatedAt + riskAssessments(first: 1, orderBy: { direction: DESC, field: ASSESSED_AT }) { + edges { + node { + id + assessedAt + expiresAt + dataSensitivity + businessImpact + } + } + } } } pageInfo { @@ -292,20 +309,42 @@ function ListVendorContent({ switch (riskLevel) { case "CRITICAL": return "bg-[#FFEFEF] text-[#CD2B31]"; - case "SIGNIFICANT": + case "HIGH": return "bg-[#FFF1E7] text-[#BD4B00]"; - default: + case "MEDIUM": + return "bg-[#FFF8E7] text-[#B54708]"; + case "LOW": return "bg-[#EEFADC] text-[#5D770D]"; + case "NONE": + return "bg-[#E6F4FF] text-[#0B4F71]"; + default: + return "bg-gray-100 text-gray-600"; } }; - // Mock risk status for demo (since the data doesn't have this field) - const getRiskStatus = (vendorId: string) => { - // Simplistic way to assign different risk levels for demo - const hash = vendorId.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0); - if (hash % 3 === 0) return "CRITICAL"; - if (hash % 3 === 1) return "SIGNIFICANT"; - return "GENERAL"; + // Get risk assessment status + const getRiskAssessmentStatus = (vendor: any) => { + const latestAssessment = vendor.riskAssessments?.edges[0]?.node; + + if (!latestAssessment) { + return { + status: "NEEDED", + dataSensitivity: null, + businessImpact: null, + isExpired: false + }; + } + + const now = new Date(); + const expiresAt = new Date(latestAssessment.expiresAt); + const isExpired = expiresAt < now; + + return { + status: isExpired ? "EXPIRED" : "VALID", + dataSensitivity: latestAssessment.dataSensitivity, + businessImpact: latestAssessment.businessImpact, + isExpired + }; }; return ( @@ -477,7 +516,7 @@ function ListVendorContent({
- Risk + Data Risk
- Compliance status + Business Risk {vendors.map((vendor) => { - const riskStatus = getRiskStatus(vendor.id); + const riskStatus = getRiskAssessmentStatus(vendor); return (
- - {riskStatus === "CRITICAL" - ? "Critical" - : riskStatus === "SIGNIFICANT" - ? "Medium" - : "General"} - + {riskStatus.status === "NEEDED" ? ( + + Assessment needed + + ) : riskStatus.status === "EXPIRED" ? ( + + Assessment expired + + ) : ( + + {riskStatus.dataSensitivity || "NONE"} + + )} - - {riskStatus === "CRITICAL" - ? "Late" - : riskStatus === "SIGNIFICANT" - ? "In Progress" - : "Completed"} - + {riskStatus.status === "NEEDED" ? ( + + Assessment needed + + ) : riskStatus.status === "EXPIRED" ? ( + + Assessment expired + + ) : ( + + {riskStatus.businessImpact || "LOW"} + + )} - + } + }} + > + Delete vendor + + +
); diff --git a/apps/console/src/pages/organizations/vendors/__generated__/ListVendorViewPaginationQuery.graphql.ts b/apps/console/src/pages/organizations/vendors/__generated__/ListVendorViewPaginationQuery.graphql.ts index c8a86ce33..bf2bf607f 100644 --- a/apps/console/src/pages/organizations/vendors/__generated__/ListVendorViewPaginationQuery.graphql.ts +++ b/apps/console/src/pages/organizations/vendors/__generated__/ListVendorViewPaginationQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<2d5b613cbbbd051c4c1f1561f55bf644>> + * @generated SignedSource<<53f7cbf9132d69012d6e13909a807576>> * @lightSyntaxTransform * @nogrep */ @@ -231,6 +231,82 @@ return { "name": "updatedAt", "storageKey": null }, + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 1 + }, + { + "kind": "Literal", + "name": "orderBy", + "value": { + "direction": "DESC", + "field": "ASSESSED_AT" + } + } + ], + "concreteType": "VendorRiskAssessmentConnection", + "kind": "LinkedField", + "name": "riskAssessments", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorRiskAssessmentEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorRiskAssessment", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v11/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "assessedAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "expiresAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dataSensitivity", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "businessImpact", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "riskAssessments(first:1,orderBy:{\"direction\":\"DESC\",\"field\":\"ASSESSED_AT\"})" + }, (v10/*: any*/) ], "storageKey": null @@ -320,16 +396,16 @@ return { ] }, "params": { - "cacheID": "b1055c67d0940ecd9189ec80410a8add", + "cacheID": "bbb819bae323711ced24c6d5ab842906", "id": null, "metadata": {}, "name": "ListVendorViewPaginationQuery", "operationKind": "query", - "text": "query ListVendorViewPaginationQuery(\n $after: CursorKey\n $before: CursorKey\n $first: Int\n $last: Int\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...ListVendorView_vendors_pbnwq\n id\n }\n}\n\nfragment ListVendorView_vendors_pbnwq on Organization {\n vendors(first: $first, after: $after, last: $last, before: $before, orderBy: {direction: ASC, field: NAME}) {\n edges {\n node {\n id\n name\n description\n websiteUrl\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n id\n}\n" + "text": "query ListVendorViewPaginationQuery(\n $after: CursorKey\n $before: CursorKey\n $first: Int\n $last: Int\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...ListVendorView_vendors_pbnwq\n id\n }\n}\n\nfragment ListVendorView_vendors_pbnwq on Organization {\n vendors(first: $first, after: $after, last: $last, before: $before, orderBy: {direction: ASC, field: NAME}) {\n edges {\n node {\n id\n name\n description\n websiteUrl\n createdAt\n updatedAt\n riskAssessments(first: 1, orderBy: {direction: DESC, field: ASSESSED_AT}) {\n edges {\n node {\n id\n assessedAt\n expiresAt\n dataSensitivity\n businessImpact\n }\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n id\n}\n" } }; })(); -(node as any).hash = "3908e5fd1af7e0cb367a480fc9fc437e"; +(node as any).hash = "eb39ac51abe77196431f4f20ac0010a9"; export default node; diff --git a/apps/console/src/pages/organizations/vendors/__generated__/ListVendorViewQuery.graphql.ts b/apps/console/src/pages/organizations/vendors/__generated__/ListVendorViewQuery.graphql.ts index a26d463b8..d9c646997 100644 --- a/apps/console/src/pages/organizations/vendors/__generated__/ListVendorViewQuery.graphql.ts +++ b/apps/console/src/pages/organizations/vendors/__generated__/ListVendorViewQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<4d1328e7df631fabe3c8b7dccdf5ffb0>> + * @generated SignedSource<<500e7888f7d464b89781689be541b2c5>> * @lightSyntaxTransform * @nogrep */ @@ -233,6 +233,82 @@ return { "name": "updatedAt", "storageKey": null }, + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 1 + }, + { + "kind": "Literal", + "name": "orderBy", + "value": { + "direction": "DESC", + "field": "ASSESSED_AT" + } + } + ], + "concreteType": "VendorRiskAssessmentConnection", + "kind": "LinkedField", + "name": "riskAssessments", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorRiskAssessmentEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorRiskAssessment", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v6/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "assessedAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "expiresAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dataSensitivity", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "businessImpact", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "riskAssessments(first:1,orderBy:{\"direction\":\"DESC\",\"field\":\"ASSESSED_AT\"})" + }, (v11/*: any*/) ], "storageKey": null @@ -322,12 +398,12 @@ return { ] }, "params": { - "cacheID": "f4269cea33bcb3665cfc49bc350cf426", + "cacheID": "7a3b85e5ebe050f67283192623ccd695", "id": null, "metadata": {}, "name": "ListVendorViewQuery", "operationKind": "query", - "text": "query ListVendorViewQuery(\n $organizationId: ID!\n $first: Int\n $after: CursorKey\n $last: Int\n $before: CursorKey\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ...ListVendorView_vendors_pbnwq\n }\n}\n\nfragment ListVendorView_vendors_pbnwq on Organization {\n vendors(first: $first, after: $after, last: $last, before: $before, orderBy: {direction: ASC, field: NAME}) {\n edges {\n node {\n id\n name\n description\n websiteUrl\n createdAt\n updatedAt\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n id\n}\n" + "text": "query ListVendorViewQuery(\n $organizationId: ID!\n $first: Int\n $after: CursorKey\n $last: Int\n $before: CursorKey\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ...ListVendorView_vendors_pbnwq\n }\n}\n\nfragment ListVendorView_vendors_pbnwq on Organization {\n vendors(first: $first, after: $after, last: $last, before: $before, orderBy: {direction: ASC, field: NAME}) {\n edges {\n node {\n id\n name\n description\n websiteUrl\n createdAt\n updatedAt\n riskAssessments(first: 1, orderBy: {direction: DESC, field: ASSESSED_AT}) {\n edges {\n node {\n id\n assessedAt\n expiresAt\n dataSensitivity\n businessImpact\n }\n }\n }\n __typename\n }\n cursor\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n id\n}\n" } }; })(); diff --git a/apps/console/src/pages/organizations/vendors/__generated__/ListVendorView_vendors.graphql.ts b/apps/console/src/pages/organizations/vendors/__generated__/ListVendorView_vendors.graphql.ts index 8ec091522..8bf7626b1 100644 --- a/apps/console/src/pages/organizations/vendors/__generated__/ListVendorView_vendors.graphql.ts +++ b/apps/console/src/pages/organizations/vendors/__generated__/ListVendorView_vendors.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -9,6 +9,8 @@ // @ts-nocheck import { ReaderFragment } from 'relay-runtime'; +export type BusinessImpact = "CRITICAL" | "HIGH" | "LOW" | "MEDIUM"; +export type DataSensitivity = "CRITICAL" | "HIGH" | "LOW" | "MEDIUM" | "NONE"; import { FragmentRefs } from "relay-runtime"; export type ListVendorView_vendors$data = { readonly id: string; @@ -20,6 +22,17 @@ export type ListVendorView_vendors$data = { readonly description: string | null | undefined; readonly id: string; readonly name: string; + readonly riskAssessments: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly assessedAt: string; + readonly businessImpact: BusinessImpact; + readonly dataSensitivity: DataSensitivity; + readonly expiresAt: string; + readonly id: string; + }; + }>; + }; readonly updatedAt: string; readonly websiteUrl: string | null | undefined; }; @@ -175,6 +188,82 @@ return { "name": "updatedAt", "storageKey": null }, + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 1 + }, + { + "kind": "Literal", + "name": "orderBy", + "value": { + "direction": "DESC", + "field": "ASSESSED_AT" + } + } + ], + "concreteType": "VendorRiskAssessmentConnection", + "kind": "LinkedField", + "name": "riskAssessments", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorRiskAssessmentEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "VendorRiskAssessment", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "assessedAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "expiresAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "dataSensitivity", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "businessImpact", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "riskAssessments(first:1,orderBy:{\"direction\":\"DESC\",\"field\":\"ASSESSED_AT\"})" + }, { "alias": null, "args": null, @@ -256,6 +345,6 @@ return { }; })(); -(node as any).hash = "3908e5fd1af7e0cb367a480fc9fc437e"; +(node as any).hash = "eb39ac51abe77196431f4f20ac0010a9"; export default node; diff --git a/pkg/coredata/vendor_risk_assessment.go b/pkg/coredata/vendor_risk_assessment.go index 44281bc6d..5e9b79a97 100644 --- a/pkg/coredata/vendor_risk_assessment.go +++ b/pkg/coredata/vendor_risk_assessment.go @@ -52,6 +52,8 @@ func (v VendorRiskAssessment) CursorKey(orderBy VendorRiskAssessmentOrderField) return page.NewCursorKey(v.ID, v.CreatedAt) case VendorRiskAssessmentOrderFieldExpiresAt: return page.NewCursorKey(v.ID, v.ExpiresAt) + case VendorRiskAssessmentOrderFieldAssessedAt: + return page.NewCursorKey(v.ID, v.AssessedAt) } panic(fmt.Sprintf("unsupported order by: %s", orderBy)) diff --git a/pkg/coredata/vendor_risk_assessment_order_field.go b/pkg/coredata/vendor_risk_assessment_order_field.go index 9b40e9af2..07bfc29cf 100644 --- a/pkg/coredata/vendor_risk_assessment_order_field.go +++ b/pkg/coredata/vendor_risk_assessment_order_field.go @@ -19,8 +19,9 @@ type ( ) const ( - VendorRiskAssessmentOrderFieldCreatedAt VendorRiskAssessmentOrderField = "CREATED_AT" - VendorRiskAssessmentOrderFieldExpiresAt VendorRiskAssessmentOrderField = "EXPIRES_AT" + VendorRiskAssessmentOrderFieldCreatedAt VendorRiskAssessmentOrderField = "CREATED_AT" + VendorRiskAssessmentOrderFieldExpiresAt VendorRiskAssessmentOrderField = "EXPIRES_AT" + VendorRiskAssessmentOrderFieldAssessedAt VendorRiskAssessmentOrderField = "ASSESSED_AT" ) func (p VendorRiskAssessmentOrderField) Column() string { diff --git a/pkg/server/api/console/v1/schema.graphql b/pkg/server/api/console/v1/schema.graphql index 2f9c5df69..e6d79549a 100644 --- a/pkg/server/api/console/v1/schema.graphql +++ b/pkg/server/api/console/v1/schema.graphql @@ -1479,6 +1479,10 @@ enum VendorRiskAssessmentOrderField @goEnum( value: "github.com/getprobo/probo/pkg/coredata.VendorRiskAssessmentOrderFieldExpiresAt" ) + ASSESSED_AT + @goEnum( + value: "github.com/getprobo/probo/pkg/coredata.VendorRiskAssessmentOrderFieldAssessedAt" + ) } input CreateVendorRiskAssessmentInput { diff --git a/pkg/server/api/console/v1/schema/schema.go b/pkg/server/api/console/v1/schema/schema.go index c8e769ed5..579427c0e 100644 --- a/pkg/server/api/console/v1/schema/schema.go +++ b/pkg/server/api/console/v1/schema/schema.go @@ -4863,6 +4863,10 @@ enum VendorRiskAssessmentOrderField @goEnum( value: "github.com/getprobo/probo/pkg/coredata.VendorRiskAssessmentOrderFieldExpiresAt" ) + ASSESSED_AT + @goEnum( + value: "github.com/getprobo/probo/pkg/coredata.VendorRiskAssessmentOrderFieldAssessedAt" + ) } input CreateVendorRiskAssessmentInput { @@ -37075,12 +37079,14 @@ func (ec *executionContext) marshalNVendorRiskAssessmentOrderField2githubᚗcom var ( unmarshalNVendorRiskAssessmentOrderField2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐVendorRiskAssessmentOrderField = map[string]coredata.VendorRiskAssessmentOrderField{ - "CREATED_AT": coredata.VendorRiskAssessmentOrderFieldCreatedAt, - "EXPIRES_AT": coredata.VendorRiskAssessmentOrderFieldExpiresAt, + "CREATED_AT": coredata.VendorRiskAssessmentOrderFieldCreatedAt, + "EXPIRES_AT": coredata.VendorRiskAssessmentOrderFieldExpiresAt, + "ASSESSED_AT": coredata.VendorRiskAssessmentOrderFieldAssessedAt, } marshalNVendorRiskAssessmentOrderField2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐVendorRiskAssessmentOrderField = map[coredata.VendorRiskAssessmentOrderField]string{ - coredata.VendorRiskAssessmentOrderFieldCreatedAt: "CREATED_AT", - coredata.VendorRiskAssessmentOrderFieldExpiresAt: "EXPIRES_AT", + coredata.VendorRiskAssessmentOrderFieldCreatedAt: "CREATED_AT", + coredata.VendorRiskAssessmentOrderFieldExpiresAt: "EXPIRES_AT", + coredata.VendorRiskAssessmentOrderFieldAssessedAt: "ASSESSED_AT", } )