From 6da223ed36aadb12b49196e8392d2554c9f561fc Mon Sep 17 00:00:00 2001 From: gearnode Date: Tue, 18 Feb 2025 11:14:20 +0100 Subject: [PATCH] Add risk tier, criticality on vendors Signed-off-by: gearnode --- apps/console/src/pages/VendorOverviewPage.tsx | 341 +++++------- .../VendorOverviewPageQuery.graphql.ts | 66 ++- pkg/api/console/v1/schema.graphql | 27 + pkg/api/console/v1/schema/schema.go | 502 +++++++++++++++++- pkg/api/console/v1/types/types.go | 23 +- pkg/api/console/v1/types/vendor.go | 13 +- .../coredata/migrations/20250218T101900Z.sql | 15 + pkg/probo/coredata/risk_tier.go | 66 +++ pkg/probo/coredata/service_criticality.go | 66 +++ pkg/probo/coredata/vendor.go | 57 +- 10 files changed, 940 insertions(+), 236 deletions(-) create mode 100644 pkg/probo/coredata/migrations/20250218T101900Z.sql create mode 100644 pkg/probo/coredata/risk_tier.go create mode 100644 pkg/probo/coredata/service_criticality.go diff --git a/apps/console/src/pages/VendorOverviewPage.tsx b/apps/console/src/pages/VendorOverviewPage.tsx index 68f02699f..2726043d3 100644 --- a/apps/console/src/pages/VendorOverviewPage.tsx +++ b/apps/console/src/pages/VendorOverviewPage.tsx @@ -1,29 +1,32 @@ "use client"; -import { useState } from "react"; import { Card } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; -import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; -import { Checkbox } from "@/components/ui/checkbox"; -import { HelpCircle } from "lucide-react"; +import { HelpCircle, ArrowUpRight } from "lucide-react"; import { graphql, PreloadedQuery, usePreloadedQuery, useQueryLoader, } from "react-relay"; -import { Suspense, useEffect } from "react"; +import { Suspense, useEffect, useState } from "react"; import type { VendorOverviewPageQuery as VendorOverviewPageQueryType } from "./__generated__/VendorOverviewPageQuery.graphql"; import { useParams } from "react-router"; import { Helmet } from "react-helmet-async"; import { useBreadcrumb } from "@/contexts/BreadcrumbContext"; +import { cn } from "@/lib/utils"; -const VendorOverviewPageQuery = graphql` +const vendorOverviewPageQuery = graphql` query VendorOverviewPageQuery($vendorId: ID!) { node(id: $vendorId) { ... on Vendor { id name + serviceStartDate + serviceTerminationDate + serviceCriticality + riskTier + statusPageUrl createdAt updatedAt } @@ -36,9 +39,10 @@ function VendorOverviewPageContent({ }: { queryRef: PreloadedQuery; }) { - const data = usePreloadedQuery(VendorOverviewPageQuery, queryRef); - const [riskLevel, setRiskLevel] = useState("medium"); + const data = usePreloadedQuery(vendorOverviewPageQuery, queryRef); const { setBreadcrumbSegment } = useBreadcrumb(); + const [selectedCriticality, setSelectedCriticality] = useState(data.node.serviceCriticality); + const [selectedRiskTier, setSelectedRiskTier] = useState(data.node.riskTier); useEffect(() => { if (data.node?.name) { @@ -50,57 +54,18 @@ function VendorOverviewPageContent({
-
- Google Cloud -

- {data.node?.name} -

-
-

- Use this form to assess the risk of using Google Workspace as a - vendor. -

+

+ {data.node?.name} +

+

View and manage vendor details

-

Asset Storage Type

+

Service Information

- Questions related to the type of assets stored by the vendor. -

-
- -
-
- - -
-
- - -
-
-
-
- - -
-
-

Data Storage and Handling

-

- Questions related to the vendor's data storage and handling - practices. + Basic information about the vendor service

@@ -108,178 +73,142 @@ function VendorOverviewPageContent({
- -
-
- - +
+

+ {new Date(data.node?.serviceStartDate).toLocaleDateString()} +

-
-
- - -
-
- - -
-
- -
-
- - -
+ {data.node?.serviceTerminationDate && (
-
- - -
-
- - -
-
- - +
+ +
+

+ {new Date(data.node?.serviceTerminationDate).toLocaleDateString()} +

-
-
-
-
- - -
-
-

- Vendor Reliability and Continuity -

-

- Evaluating the vendor's reliability and plans for business - continuity. -

-
- -
-
-
- - -
- -
- - -
-
- - -
-
- - -
-
- - -
-
-
+ )}
- +
- - -
-
- -
-
- - -
-
- - -
+

+ {selectedCriticality === 'HIGH' && + "Critical service - downtime severely impacts end-users"} + {selectedCriticality === 'MEDIUM' && + "Important service - downtime moderately affects end-users"} + {selectedCriticality === 'LOW' && + "Non-critical service - minimal end-user impact if down"} +

-
-
-
- -
-
-

Risk level

-

- Based on the responses, the suggested risk level is medium. -

-
-
- - - +
+
+ + +
+
+ + + +
+

+ {selectedRiskTier === 'CRITICAL' && + "Handles sensitive data, critical for platform operation"} + {selectedRiskTier === 'SIGNIFICANT' && + "No user data access, but important for platform management"} + {selectedRiskTier === 'GENERAL' && + "General vendor with minimal risk"} +

+
+ + {data.node?.statusPageUrl && ( +
+
+ + +
+ + View Status Page + + +
+ )}
@@ -296,7 +225,7 @@ function VendorOverviewPageFallback() {
- {[1, 2, 3].map((i) => ( + {[1, 2].map((i) => (
))}
@@ -307,7 +236,7 @@ function VendorOverviewPageFallback() { export default function VendorOverviewPage() { const { vendorId } = useParams(); const [queryRef, loadQuery] = useQueryLoader( - VendorOverviewPageQuery, + vendorOverviewPageQuery, ); useEffect(() => { diff --git a/apps/console/src/pages/__generated__/VendorOverviewPageQuery.graphql.ts b/apps/console/src/pages/__generated__/VendorOverviewPageQuery.graphql.ts index a22243711..a2319d976 100644 --- a/apps/console/src/pages/__generated__/VendorOverviewPageQuery.graphql.ts +++ b/apps/console/src/pages/__generated__/VendorOverviewPageQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<939b83dd396154844197ee10514be613>> * @lightSyntaxTransform * @nogrep */ @@ -9,6 +9,8 @@ // @ts-nocheck import { ConcreteRequest } from 'relay-runtime'; +export type RiskTier = "CRITICAL" | "GENERAL" | "SIGNIFICANT"; +export type ServiceCriticality = "HIGH" | "LOW" | "MEDIUM"; export type VendorOverviewPageQuery$variables = { vendorId: string; }; @@ -17,6 +19,11 @@ export type VendorOverviewPageQuery$data = { readonly createdAt?: any; readonly id?: string; readonly name?: string; + readonly riskTier?: RiskTier; + readonly serviceCriticality?: ServiceCriticality; + readonly serviceStartDate?: any; + readonly serviceTerminationDate?: any | null | undefined; + readonly statusPageUrl?: string | null | undefined; readonly updatedAt?: any; }; }; @@ -58,10 +65,45 @@ v4 = { "alias": null, "args": null, "kind": "ScalarField", - "name": "createdAt", + "name": "serviceStartDate", "storageKey": null }, v5 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "serviceTerminationDate", + "storageKey": null +}, +v6 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "serviceCriticality", + "storageKey": null +}, +v7 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "riskTier", + "storageKey": null +}, +v8 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "statusPageUrl", + "storageKey": null +}, +v9 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null +}, +v10 = { "alias": null, "args": null, "kind": "ScalarField", @@ -89,7 +131,12 @@ return { (v2/*: any*/), (v3/*: any*/), (v4/*: any*/), - (v5/*: any*/) + (v5/*: any*/), + (v6/*: any*/), + (v7/*: any*/), + (v8/*: any*/), + (v9/*: any*/), + (v10/*: any*/) ], "type": "Vendor", "abstractKey": null @@ -128,7 +175,12 @@ return { "selections": [ (v3/*: any*/), (v4/*: any*/), - (v5/*: any*/) + (v5/*: any*/), + (v6/*: any*/), + (v7/*: any*/), + (v8/*: any*/), + (v9/*: any*/), + (v10/*: any*/) ], "type": "Vendor", "abstractKey": null @@ -139,16 +191,16 @@ return { ] }, "params": { - "cacheID": "93086ca61d518fce9c6ed49fa9bd841e", + "cacheID": "c9d7bade02d1802838f6ac3b02ef7c9a", "id": null, "metadata": {}, "name": "VendorOverviewPageQuery", "operationKind": "query", - "text": "query VendorOverviewPageQuery(\n $vendorId: ID!\n) {\n node(id: $vendorId) {\n __typename\n ... on Vendor {\n id\n name\n createdAt\n updatedAt\n }\n id\n }\n}\n" + "text": "query VendorOverviewPageQuery(\n $vendorId: ID!\n) {\n node(id: $vendorId) {\n __typename\n ... on Vendor {\n id\n name\n serviceStartDate\n serviceTerminationDate\n serviceCriticality\n riskTier\n statusPageUrl\n createdAt\n updatedAt\n }\n id\n }\n}\n" } }; })(); -(node as any).hash = "7959720cf1a6376d406ac5f478f6d9c3"; +(node as any).hash = "92a640f674261d28028d752f027ec141"; export default node; diff --git a/pkg/api/console/v1/schema.graphql b/pkg/api/console/v1/schema.graphql index 1dc8383fb..8929d564e 100644 --- a/pkg/api/console/v1/schema.graphql +++ b/pkg/api/console/v1/schema.graphql @@ -117,6 +117,11 @@ type Vendor implements Node { name: String! createdAt: Datetime! updatedAt: Datetime! + serviceStartDate: Datetime! + serviceTerminationDate: Datetime + serviceCriticality: ServiceCriticality! + riskTier: RiskTier! + statusPageUrl: String } type FrameworkConnection { @@ -328,4 +333,26 @@ input CreatePeopleInput { fullName: String! primaryEmailAddress: String! kind: PeopleKind! +} + +enum ServiceCriticality @goModel(model: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticality") { + LOW @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityLow") + MEDIUM @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityMedium") + HIGH @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityHigh") +} + +enum RiskTier @goModel(model: "github.com/getprobo/probo/pkg/probo/coredata.RiskTier") { + CRITICAL @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.RiskTierCritical") + SIGNIFICANT @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.RiskTierSignificant") + GENERAL @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.RiskTierGeneral") +} + +input UpdateVendorInput { + id: ID! + name: String + serviceStartDate: Datetime + serviceTerminationDate: Datetime + serviceCriticality: ServiceCriticality + riskTier: RiskTier + statusPageUrl: String } \ No newline at end of file diff --git a/pkg/api/console/v1/schema/schema.go b/pkg/api/console/v1/schema/schema.go index 37a199473..ca13a2958 100644 --- a/pkg/api/console/v1/schema/schema.go +++ b/pkg/api/console/v1/schema/schema.go @@ -245,10 +245,15 @@ type ComplexityRoot struct { } Vendor struct { - CreatedAt func(childComplexity int) int - ID func(childComplexity int) int - Name func(childComplexity int) int - UpdatedAt func(childComplexity int) int + CreatedAt func(childComplexity int) int + ID func(childComplexity int) int + Name func(childComplexity int) int + RiskTier func(childComplexity int) int + ServiceCriticality func(childComplexity int) int + ServiceStartDate func(childComplexity int) int + ServiceTerminationDate func(childComplexity int) int + StatusPageURL func(childComplexity int) int + UpdatedAt func(childComplexity int) int } VendorConnection struct { @@ -1136,6 +1141,41 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Vendor.Name(childComplexity), true + case "Vendor.riskTier": + if e.complexity.Vendor.RiskTier == nil { + break + } + + return e.complexity.Vendor.RiskTier(childComplexity), true + + case "Vendor.serviceCriticality": + if e.complexity.Vendor.ServiceCriticality == nil { + break + } + + return e.complexity.Vendor.ServiceCriticality(childComplexity), true + + case "Vendor.serviceStartDate": + if e.complexity.Vendor.ServiceStartDate == nil { + break + } + + return e.complexity.Vendor.ServiceStartDate(childComplexity), true + + case "Vendor.serviceTerminationDate": + if e.complexity.Vendor.ServiceTerminationDate == nil { + break + } + + return e.complexity.Vendor.ServiceTerminationDate(childComplexity), true + + case "Vendor.statusPageUrl": + if e.complexity.Vendor.StatusPageURL == nil { + break + } + + return e.complexity.Vendor.StatusPageURL(childComplexity), true + case "Vendor.updatedAt": if e.complexity.Vendor.UpdatedAt == nil { break @@ -1183,6 +1223,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputCreateVendorInput, ec.unmarshalInputDeletePeopleInput, ec.unmarshalInputDeleteVendorInput, + ec.unmarshalInputUpdateVendorInput, ) first := true @@ -1399,6 +1440,11 @@ type Vendor implements Node { name: String! createdAt: Datetime! updatedAt: Datetime! + serviceStartDate: Datetime! + serviceTerminationDate: Datetime + serviceCriticality: ServiceCriticality! + riskTier: RiskTier! + statusPageUrl: String } type FrameworkConnection { @@ -1610,6 +1656,28 @@ input CreatePeopleInput { fullName: String! primaryEmailAddress: String! kind: PeopleKind! +} + +enum ServiceCriticality @goModel(model: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticality") { + LOW @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityLow") + MEDIUM @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityMedium") + HIGH @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.ServiceCriticalityHigh") +} + +enum RiskTier @goModel(model: "github.com/getprobo/probo/pkg/probo/coredata.RiskTier") { + CRITICAL @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.RiskTierCritical") + SIGNIFICANT @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.RiskTierSignificant") + GENERAL @goEnum(value: "github.com/getprobo/probo/pkg/probo/coredata.RiskTierGeneral") +} + +input UpdateVendorInput { + id: ID! + name: String + serviceStartDate: Datetime + serviceTerminationDate: Datetime + serviceCriticality: ServiceCriticality + riskTier: RiskTier + statusPageUrl: String }`, BuiltIn: false}, } var parsedSchema = gqlparser.MustLoadSchema(sources...) @@ -4826,6 +4894,16 @@ func (ec *executionContext) fieldContext_Mutation_createVendor(ctx context.Conte return ec.fieldContext_Vendor_createdAt(ctx, field) case "updatedAt": return ec.fieldContext_Vendor_updatedAt(ctx, field) + case "serviceStartDate": + return ec.fieldContext_Vendor_serviceStartDate(ctx, field) + case "serviceTerminationDate": + return ec.fieldContext_Vendor_serviceTerminationDate(ctx, field) + case "serviceCriticality": + return ec.fieldContext_Vendor_serviceCriticality(ctx, field) + case "riskTier": + return ec.fieldContext_Vendor_riskTier(ctx, field) + case "statusPageUrl": + return ec.fieldContext_Vendor_statusPageUrl(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Vendor", field.Name) }, @@ -7138,6 +7216,190 @@ func (ec *executionContext) fieldContext_Vendor_updatedAt(_ context.Context, fie return fc, nil } +func (ec *executionContext) _Vendor_serviceStartDate(ctx context.Context, field graphql.CollectedField, obj *types.Vendor) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Vendor_serviceStartDate(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.ServiceStartDate, 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.(time.Time) + fc.Result = res + return ec.marshalNDatetime2timeᚐTime(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Vendor_serviceStartDate(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Vendor", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Datetime does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Vendor_serviceTerminationDate(ctx context.Context, field graphql.CollectedField, obj *types.Vendor) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Vendor_serviceTerminationDate(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.ServiceTerminationDate, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*time.Time) + fc.Result = res + return ec.marshalODatetime2ᚖtimeᚐTime(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Vendor_serviceTerminationDate(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Vendor", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Datetime does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Vendor_serviceCriticality(ctx context.Context, field graphql.CollectedField, obj *types.Vendor) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Vendor_serviceCriticality(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.ServiceCriticality, 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.(coredata.ServiceCriticality) + fc.Result = res + return ec.marshalNServiceCriticality2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐServiceCriticality(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Vendor_serviceCriticality(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Vendor", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ServiceCriticality does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Vendor_riskTier(ctx context.Context, field graphql.CollectedField, obj *types.Vendor) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Vendor_riskTier(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.RiskTier, 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.(coredata.RiskTier) + fc.Result = res + return ec.marshalNRiskTier2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐRiskTier(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Vendor_riskTier(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Vendor", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type RiskTier does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Vendor_statusPageUrl(ctx context.Context, field graphql.CollectedField, obj *types.Vendor) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Vendor_statusPageUrl(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) { + ctx = rctx // use context from middleware stack in children + return obj.StatusPageURL, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Vendor_statusPageUrl(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Vendor", + 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) _VendorConnection_edges(ctx context.Context, field graphql.CollectedField, obj *types.VendorConnection) (ret graphql.Marshaler) { fc, err := ec.fieldContext_VendorConnection_edges(ctx, field) if err != nil { @@ -7309,6 +7571,16 @@ func (ec *executionContext) fieldContext_VendorEdge_node(_ context.Context, fiel return ec.fieldContext_Vendor_createdAt(ctx, field) case "updatedAt": return ec.fieldContext_Vendor_updatedAt(ctx, field) + case "serviceStartDate": + return ec.fieldContext_Vendor_serviceStartDate(ctx, field) + case "serviceTerminationDate": + return ec.fieldContext_Vendor_serviceTerminationDate(ctx, field) + case "serviceCriticality": + return ec.fieldContext_Vendor_serviceCriticality(ctx, field) + case "riskTier": + return ec.fieldContext_Vendor_riskTier(ctx, field) + case "statusPageUrl": + return ec.fieldContext_Vendor_statusPageUrl(ctx, field) } return nil, fmt.Errorf("no field named %q was found under type Vendor", field.Name) }, @@ -9003,6 +9275,75 @@ func (ec *executionContext) unmarshalInputDeleteVendorInput(ctx context.Context, return it, nil } +func (ec *executionContext) unmarshalInputUpdateVendorInput(ctx context.Context, obj any) (types.UpdateVendorInput, error) { + var it types.UpdateVendorInput + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"id", "name", "serviceStartDate", "serviceTerminationDate", "serviceCriticality", "riskTier", "statusPageUrl"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "id": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("id")) + data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v) + if err != nil { + return it, err + } + it.ID = data + case "name": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.Name = data + case "serviceStartDate": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("serviceStartDate")) + data, err := ec.unmarshalODatetime2ᚖtimeᚐTime(ctx, v) + if err != nil { + return it, err + } + it.ServiceStartDate = data + case "serviceTerminationDate": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("serviceTerminationDate")) + data, err := ec.unmarshalODatetime2ᚖtimeᚐTime(ctx, v) + if err != nil { + return it, err + } + it.ServiceTerminationDate = data + case "serviceCriticality": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("serviceCriticality")) + data, err := ec.unmarshalOServiceCriticality2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐServiceCriticality(ctx, v) + if err != nil { + return it, err + } + it.ServiceCriticality = data + case "riskTier": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("riskTier")) + data, err := ec.unmarshalORiskTier2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐRiskTier(ctx, v) + if err != nil { + return it, err + } + it.RiskTier = data + case "statusPageUrl": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("statusPageUrl")) + data, err := ec.unmarshalOString2ᚖstring(ctx, v) + if err != nil { + return it, err + } + it.StatusPageURL = data + } + } + + return it, nil +} + // endregion **************************** input.gotpl ***************************** // region ************************** interface.gotpl *************************** @@ -10831,6 +11172,25 @@ func (ec *executionContext) _Vendor(ctx context.Context, sel ast.SelectionSet, o if out.Values[i] == graphql.Null { out.Invalids++ } + case "serviceStartDate": + out.Values[i] = ec._Vendor_serviceStartDate(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "serviceTerminationDate": + out.Values[i] = ec._Vendor_serviceTerminationDate(ctx, field, obj) + case "serviceCriticality": + out.Values[i] = ec._Vendor_serviceCriticality(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "riskTier": + out.Values[i] = ec._Vendor_riskTier(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "statusPageUrl": + out.Values[i] = ec._Vendor_statusPageUrl(ctx, field, obj) default: panic("unknown field " + strconv.Quote(field.Name)) } @@ -11906,6 +12266,64 @@ var ( } ) +func (ec *executionContext) unmarshalNRiskTier2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐRiskTier(ctx context.Context, v any) (coredata.RiskTier, error) { + tmp, err := graphql.UnmarshalString(v) + res := unmarshalNRiskTier2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐRiskTier[tmp] + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNRiskTier2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐRiskTier(ctx context.Context, sel ast.SelectionSet, v coredata.RiskTier) graphql.Marshaler { + res := graphql.MarshalString(marshalNRiskTier2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐRiskTier[v]) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +var ( + unmarshalNRiskTier2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐRiskTier = map[string]coredata.RiskTier{ + "CRITICAL": coredata.RiskTierCritical, + "SIGNIFICANT": coredata.RiskTierSignificant, + "GENERAL": coredata.RiskTierGeneral, + } + marshalNRiskTier2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐRiskTier = map[coredata.RiskTier]string{ + coredata.RiskTierCritical: "CRITICAL", + coredata.RiskTierSignificant: "SIGNIFICANT", + coredata.RiskTierGeneral: "GENERAL", + } +) + +func (ec *executionContext) unmarshalNServiceCriticality2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐServiceCriticality(ctx context.Context, v any) (coredata.ServiceCriticality, error) { + tmp, err := graphql.UnmarshalString(v) + res := unmarshalNServiceCriticality2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐServiceCriticality[tmp] + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNServiceCriticality2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐServiceCriticality(ctx context.Context, sel ast.SelectionSet, v coredata.ServiceCriticality) graphql.Marshaler { + res := graphql.MarshalString(marshalNServiceCriticality2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐServiceCriticality[v]) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +var ( + unmarshalNServiceCriticality2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐServiceCriticality = map[string]coredata.ServiceCriticality{ + "LOW": coredata.ServiceCriticalityLow, + "MEDIUM": coredata.ServiceCriticalityMedium, + "HIGH": coredata.ServiceCriticalityHigh, + } + marshalNServiceCriticality2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐServiceCriticality = map[coredata.ServiceCriticality]string{ + coredata.ServiceCriticalityLow: "LOW", + coredata.ServiceCriticalityMedium: "MEDIUM", + coredata.ServiceCriticalityHigh: "HIGH", + } +) + func (ec *executionContext) unmarshalNString2string(ctx context.Context, v any) (string, error) { res, err := graphql.UnmarshalString(v) return res, graphql.ErrorOnPath(ctx, err) @@ -12518,6 +12936,22 @@ func (ec *executionContext) marshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋprobo return res } +func (ec *executionContext) unmarshalODatetime2ᚖtimeᚐTime(ctx context.Context, v any) (*time.Time, error) { + if v == nil { + return nil, nil + } + res, err := graphql.UnmarshalTime(v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalODatetime2ᚖtimeᚐTime(ctx context.Context, sel ast.SelectionSet, v *time.Time) graphql.Marshaler { + if v == nil { + return graphql.Null + } + res := graphql.MarshalTime(*v) + return res +} + func (ec *executionContext) unmarshalOEvidenceState2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐEvidenceState(ctx context.Context, v any) (*coredata.EvidenceState, error) { if v == nil { return nil, nil @@ -12564,6 +12998,66 @@ func (ec *executionContext) marshalOInt2ᚖint(ctx context.Context, sel ast.Sele return res } +func (ec *executionContext) unmarshalORiskTier2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐRiskTier(ctx context.Context, v any) (*coredata.RiskTier, error) { + if v == nil { + return nil, nil + } + tmp, err := graphql.UnmarshalString(v) + res := unmarshalORiskTier2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐRiskTier[tmp] + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalORiskTier2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐRiskTier(ctx context.Context, sel ast.SelectionSet, v *coredata.RiskTier) graphql.Marshaler { + if v == nil { + return graphql.Null + } + res := graphql.MarshalString(marshalORiskTier2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐRiskTier[*v]) + return res +} + +var ( + unmarshalORiskTier2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐRiskTier = map[string]coredata.RiskTier{ + "CRITICAL": coredata.RiskTierCritical, + "SIGNIFICANT": coredata.RiskTierSignificant, + "GENERAL": coredata.RiskTierGeneral, + } + marshalORiskTier2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐRiskTier = map[coredata.RiskTier]string{ + coredata.RiskTierCritical: "CRITICAL", + coredata.RiskTierSignificant: "SIGNIFICANT", + coredata.RiskTierGeneral: "GENERAL", + } +) + +func (ec *executionContext) unmarshalOServiceCriticality2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐServiceCriticality(ctx context.Context, v any) (*coredata.ServiceCriticality, error) { + if v == nil { + return nil, nil + } + tmp, err := graphql.UnmarshalString(v) + res := unmarshalOServiceCriticality2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐServiceCriticality[tmp] + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalOServiceCriticality2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐServiceCriticality(ctx context.Context, sel ast.SelectionSet, v *coredata.ServiceCriticality) graphql.Marshaler { + if v == nil { + return graphql.Null + } + res := graphql.MarshalString(marshalOServiceCriticality2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐServiceCriticality[*v]) + return res +} + +var ( + unmarshalOServiceCriticality2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐServiceCriticality = map[string]coredata.ServiceCriticality{ + "LOW": coredata.ServiceCriticalityLow, + "MEDIUM": coredata.ServiceCriticalityMedium, + "HIGH": coredata.ServiceCriticalityHigh, + } + marshalOServiceCriticality2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚐServiceCriticality = map[coredata.ServiceCriticality]string{ + coredata.ServiceCriticalityLow: "LOW", + coredata.ServiceCriticalityMedium: "MEDIUM", + coredata.ServiceCriticalityHigh: "HIGH", + } +) + func (ec *executionContext) unmarshalOString2ᚕstringᚄ(ctx context.Context, v any) ([]string, error) { if v == nil { return nil, nil diff --git a/pkg/api/console/v1/types/types.go b/pkg/api/console/v1/types/types.go index edaa27f0a..ecce84622 100644 --- a/pkg/api/console/v1/types/types.go +++ b/pkg/api/console/v1/types/types.go @@ -237,11 +237,26 @@ type TaskStateTransitionEdge struct { Node *TaskStateTransition `json:"node"` } +type UpdateVendorInput struct { + ID gid.GID `json:"id"` + Name *string `json:"name,omitempty"` + ServiceStartDate *time.Time `json:"serviceStartDate,omitempty"` + ServiceTerminationDate *time.Time `json:"serviceTerminationDate,omitempty"` + ServiceCriticality *coredata.ServiceCriticality `json:"serviceCriticality,omitempty"` + RiskTier *coredata.RiskTier `json:"riskTier,omitempty"` + StatusPageURL *string `json:"statusPageUrl,omitempty"` +} + type Vendor struct { - ID gid.GID `json:"id"` - Name string `json:"name"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` + ID gid.GID `json:"id"` + Name string `json:"name"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + ServiceStartDate time.Time `json:"serviceStartDate"` + ServiceTerminationDate *time.Time `json:"serviceTerminationDate,omitempty"` + ServiceCriticality coredata.ServiceCriticality `json:"serviceCriticality"` + RiskTier coredata.RiskTier `json:"riskTier"` + StatusPageURL *string `json:"statusPageUrl,omitempty"` } func (Vendor) IsNode() {} diff --git a/pkg/api/console/v1/types/vendor.go b/pkg/api/console/v1/types/vendor.go index 0f6213263..3491a321f 100644 --- a/pkg/api/console/v1/types/vendor.go +++ b/pkg/api/console/v1/types/vendor.go @@ -41,9 +41,14 @@ func NewVendorEdge(v *coredata.Vendor) *VendorEdge { func NewVendor(v *coredata.Vendor) *Vendor { return &Vendor{ - ID: v.ID, - Name: v.Name, - CreatedAt: v.CreatedAt, - UpdatedAt: v.UpdatedAt, + ID: v.ID, + Name: v.Name, + CreatedAt: v.CreatedAt, + UpdatedAt: v.UpdatedAt, + ServiceStartDate: v.ServiceStartDate, + ServiceTerminationDate: v.ServiceTerminationDate, + ServiceCriticality: v.ServiceCriticality, + RiskTier: v.RiskTier, + StatusPageURL: v.StatusPageURL, } } diff --git a/pkg/probo/coredata/migrations/20250218T101900Z.sql b/pkg/probo/coredata/migrations/20250218T101900Z.sql new file mode 100644 index 000000000..5ee39e08f --- /dev/null +++ b/pkg/probo/coredata/migrations/20250218T101900Z.sql @@ -0,0 +1,15 @@ +CREATE TYPE service_criticality AS ENUM ('LOW', 'MEDIUM', 'HIGH'); +CREATE TYPE risk_tier AS ENUM ('CRITICAL', 'SIGNIFICANT', 'GENERAL'); + +ALTER TABLE vendors + ADD COLUMN service_start_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + ADD COLUMN service_termination_date TIMESTAMP WITH TIME ZONE, + ADD COLUMN service_criticality service_criticality NOT NULL DEFAULT 'LOW', + ADD COLUMN risk_tier risk_tier NOT NULL DEFAULT 'GENERAL', + ADD COLUMN status_page_url TEXT; + +UPDATE vendors SET service_start_date = created_at; + +ALTER TABLE vendors ALTER COLUMN service_start_date DROP DEFAULT; +ALTER TABLE vendors ALTER COLUMN service_criticality DROP DEFAULT; +ALTER TABLE vendors ALTER COLUMN risk_tier DROP DEFAULT; \ No newline at end of file diff --git a/pkg/probo/coredata/risk_tier.go b/pkg/probo/coredata/risk_tier.go new file mode 100644 index 000000000..45c1a7b8e --- /dev/null +++ b/pkg/probo/coredata/risk_tier.go @@ -0,0 +1,66 @@ +// 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 coredata + +import ( + "database/sql/driver" + "fmt" +) + +type RiskTier string + +const ( + RiskTierCritical RiskTier = "CRITICAL" // Handles sensitive data, critical for platform operation + RiskTierSignificant RiskTier = "SIGNIFICANT" // No user data access, but important for platform management + RiskTierGeneral RiskTier = "GENERAL" // General vendor with minimal risk +) + +func (rt RiskTier) MarshalText() ([]byte, error) { + return []byte(rt.String()), nil +} + +func (rt *RiskTier) UnmarshalText(data []byte) error { + val := string(data) + + switch val { + case RiskTierCritical.String(): + *rt = RiskTierCritical + case RiskTierSignificant.String(): + *rt = RiskTierSignificant + case RiskTierGeneral.String(): + *rt = RiskTierGeneral + default: + return fmt.Errorf("invalid RiskTier value: %q", val) + } + + return nil +} + +func (rt RiskTier) String() string { + return string(rt) +} + +func (rt *RiskTier) Scan(value any) error { + val, ok := value.(string) + if !ok { + return fmt.Errorf("invalid scan source for RiskTier, expected string got %T", value) + } + + return rt.UnmarshalText([]byte(val)) +} + +func (rt RiskTier) Value() (driver.Value, error) { + return rt.String(), nil +} diff --git a/pkg/probo/coredata/service_criticality.go b/pkg/probo/coredata/service_criticality.go new file mode 100644 index 000000000..b5b94aecb --- /dev/null +++ b/pkg/probo/coredata/service_criticality.go @@ -0,0 +1,66 @@ +// 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 coredata + +import ( + "database/sql/driver" + "fmt" +) + +type ServiceCriticality string + +const ( + ServiceCriticalityLow ServiceCriticality = "LOW" + ServiceCriticalityMedium ServiceCriticality = "MEDIUM" + ServiceCriticalityHigh ServiceCriticality = "HIGH" +) + +func (sc ServiceCriticality) MarshalText() ([]byte, error) { + return []byte(sc.String()), nil +} + +func (sc *ServiceCriticality) UnmarshalText(data []byte) error { + val := string(data) + + switch val { + case ServiceCriticalityLow.String(): + *sc = ServiceCriticalityLow + case ServiceCriticalityMedium.String(): + *sc = ServiceCriticalityMedium + case ServiceCriticalityHigh.String(): + *sc = ServiceCriticalityHigh + default: + return fmt.Errorf("invalid ServiceCriticality value: %q", val) + } + + return nil +} + +func (sc ServiceCriticality) String() string { + return string(sc) +} + +func (sc *ServiceCriticality) Scan(value any) error { + val, ok := value.(string) + if !ok { + return fmt.Errorf("invalid scan source for ServiceCriticality, expected string got %T", value) + } + + return sc.UnmarshalText([]byte(val)) +} + +func (sc ServiceCriticality) Value() (driver.Value, error) { + return sc.String(), nil +} diff --git a/pkg/probo/coredata/vendor.go b/pkg/probo/coredata/vendor.go index b3817189b..d72a4d7a7 100644 --- a/pkg/probo/coredata/vendor.go +++ b/pkg/probo/coredata/vendor.go @@ -28,11 +28,16 @@ import ( type ( Vendor struct { - ID gid.GID - OrganizationID gid.GID - Name string - CreatedAt time.Time - UpdatedAt time.Time + ID gid.GID + OrganizationID gid.GID + Name string + ServiceStartDate time.Time + ServiceTerminationDate *time.Time + ServiceCriticality ServiceCriticality + RiskTier RiskTier + StatusPageURL *string + CreatedAt time.Time + UpdatedAt time.Time } Vendors []*Vendor @@ -47,6 +52,11 @@ func (v *Vendor) scan(r pgx.Row) error { &v.ID, &v.OrganizationID, &v.Name, + &v.ServiceStartDate, + &v.ServiceTerminationDate, + &v.ServiceCriticality, + &v.RiskTier, + &v.StatusPageURL, &v.CreatedAt, &v.UpdatedAt, ) @@ -63,6 +73,11 @@ SELECT id, organization_id, name, + service_start_date, + service_termination_date, + service_criticality, + risk_tier, + status_page_url, created_at, updated_at FROM @@ -100,6 +115,11 @@ INSERT INTO id, organization_id, name, + service_start_date, + service_termination_date, + service_criticality, + risk_tier, + status_page_url, created_at, updated_at ) @@ -107,17 +127,27 @@ VALUES ( @vendor_id, @organization_id, @name, + @service_start_date, + @service_termination_date, + @service_criticality, + @risk_tier, + @status_page_url, @created_at, @updated_at ) ` args := pgx.NamedArgs{ - "vendor_id": v.ID, - "organization_id": v.OrganizationID, - "name": v.Name, - "created_at": v.CreatedAt, - "updated_at": v.UpdatedAt, + "vendor_id": v.ID, + "organization_id": v.OrganizationID, + "name": v.Name, + "service_start_date": v.ServiceStartDate, + "service_termination_date": v.ServiceTerminationDate, + "service_criticality": v.ServiceCriticality, + "risk_tier": v.RiskTier, + "status_page_url": v.StatusPageURL, + "created_at": v.CreatedAt, + "updated_at": v.UpdatedAt, } _, err := conn.Exec(ctx, q, args) return err @@ -153,6 +183,11 @@ SELECT id, organization_id, name, + service_start_date, + service_termination_date, + service_criticality, + risk_tier, + status_page_url, created_at, updated_at FROM @@ -167,7 +202,7 @@ WHERE args := pgx.NamedArgs{"organization_id": organizationID} maps.Copy(args, cursor.SQLArguments()) - maps.Copy(args, cursor.SQLArguments()) + maps.Copy(args, scope.SQLArguments()) r, err := conn.Query(ctx, q, args) if err != nil {