Add risk tier, criticality on vendors

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-02-18 11:14:20 +01:00
parent 53bceb5479
commit 6da223ed36
10 changed files with 940 additions and 236 deletions

View File

@@ -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<VendorOverviewPageQueryType>;
}) {
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({
<div className="space-y-6 p-4 md:p-6 lg:p-8">
<div className="mx-auto max-w-4xl space-y-6">
<div className="space-y-2">
<div className="flex items-center gap-2">
<img
src="https://hebbkx1anhila5yf.public.blob.vercel-storage.com/image-G4dMt0eis6LFLrCaYPeMr7Nkt2iPoj.png"
alt="Google Cloud"
className="h-8 w-8"
/>
<h1 className="text-xl font-semibold text-gray-900">
{data.node?.name}
</h1>
</div>
<p className="text-gray-600">
Use this form to assess the risk of using Google Workspace as a
vendor.
</p>
<h1 className="text-xl font-semibold text-gray-900">
{data.node?.name}
</h1>
<p className="text-gray-600">View and manage vendor details</p>
</div>
<Card className="p-6">
<div className="space-y-4">
<div className="space-y-2">
<h2 className="text-lg font-medium">Asset Storage Type</h2>
<h2 className="text-lg font-medium">Service Information</h2>
<p className="text-sm text-gray-500">
Questions related to the type of assets stored by the vendor.
</p>
</div>
<div className="space-y-2">
<div className="flex items-center gap-2">
<HelpCircle className="h-4 w-4 text-gray-400" />
<Label className="text-sm">
Does this vendor store physical or digital assets?
</Label>
</div>
<div className="flex gap-2">
<button className="rounded-full bg-gray-100 px-4 py-1 text-sm text-gray-900">
Digital
</button>
<button className="rounded-full bg-green-100 px-4 py-1 text-sm text-green-900">
Physical
</button>
</div>
</div>
</div>
</Card>
<Card className="p-6">
<div className="space-y-4">
<div className="space-y-2">
<h2 className="text-lg font-medium">Data Storage and Handling</h2>
<p className="text-sm text-gray-500">
Questions related to the vendor's data storage and handling
practices.
Basic information about the vendor service
</p>
</div>
@@ -108,178 +73,142 @@ function VendorOverviewPageContent({
<div className="space-y-2">
<div className="flex items-center gap-2">
<HelpCircle className="h-4 w-4 text-gray-400" />
<Label className="text-sm">
Does this vendor store production data?
</Label>
</div>
<div className="flex gap-2">
<button className="rounded-full bg-gray-100 px-4 py-1 text-sm text-gray-900">
No
</button>
<button className="rounded-full bg-green-100 px-4 py-1 text-sm text-green-900">
Yes
</button>
<Label className="text-sm">Service Start Date</Label>
</div>
<p className="text-sm text-gray-600">
{new Date(data.node?.serviceStartDate).toLocaleDateString()}
</p>
</div>
<div className="space-y-2">
<div className="flex items-center gap-2">
<HelpCircle className="h-4 w-4 text-gray-400" />
<Label className="text-sm">
Is the production data encrypted at rest?
</Label>
</div>
<div className="flex gap-2">
<button className="rounded-full bg-gray-100 px-4 py-1 text-sm text-gray-900">
No
</button>
<button className="rounded-full bg-green-100 px-4 py-1 text-sm text-green-900">
Yes
</button>
</div>
</div>
<div className="space-y-2">
<div className="flex items-center gap-2">
<HelpCircle className="h-4 w-4 text-gray-400" />
<Label className="text-sm">
Who has access to the production data?
</Label>
</div>
{data.node?.serviceTerminationDate && (
<div className="space-y-2">
<div className="flex items-center space-x-2">
<Checkbox id="vendor-staff" defaultChecked />
<label
htmlFor="vendor-staff"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Vendor Staff
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox id="third-parties" />
<label
htmlFor="third-parties"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Third Parties
</label>
</div>
<div className="flex items-center space-x-2">
<Checkbox id="automated-systems" />
<label
htmlFor="automated-systems"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Automated Systems
</label>
<div className="flex items-center gap-2">
<HelpCircle className="h-4 w-4 text-gray-400" />
<Label className="text-sm">Service Termination Date</Label>
</div>
<p className="text-sm text-gray-600">
{new Date(data.node?.serviceTerminationDate).toLocaleDateString()}
</p>
</div>
</div>
</div>
</div>
</Card>
<Card className="p-6">
<div className="space-y-4">
<div className="space-y-2">
<h2 className="text-lg font-medium">
Vendor Reliability and Continuity
</h2>
<p className="text-sm text-gray-500">
Evaluating the vendor's reliability and plans for business
continuity.
</p>
</div>
<div className="space-y-4">
<div className="space-y-2">
<div className="flex items-center gap-2">
<HelpCircle className="h-4 w-4 text-gray-400" />
<Label className="text-sm">
Does the vendor have an uptime commitment?
</Label>
</div>
<RadioGroup defaultValue="greater" className="space-y-2">
<div className="flex items-center space-x-2">
<RadioGroupItem value="greater" id="greater" />
<Label htmlFor="greater">Greater than 99.9% uptime</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="between" id="between" />
<Label htmlFor="between">
Between 99% and 99.9% uptime
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="less" id="less" />
<Label htmlFor="less">Less than 99% uptime</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="na" id="na" />
<Label htmlFor="na">Not applicable</Label>
</div>
</RadioGroup>
</div>
)}
<div className="space-y-2">
<div className="flex items-center gap-2">
<HelpCircle className="h-4 w-4 text-gray-400" />
<Label className="text-sm">
Does the vendor have a disaster recovery plan?
</Label>
<Label className="text-sm">Service Criticality</Label>
</div>
<div className="flex gap-2">
<button className="rounded-full bg-gray-100 px-4 py-1 text-sm text-gray-900">
No
</button>
<button className="rounded-full bg-green-100 px-4 py-1 text-sm text-green-900">
Yes
</button>
</div>
</div>
<div className="space-y-2">
<div className="flex items-center gap-2">
<HelpCircle className="h-4 w-4 text-gray-400" />
<Label className="text-sm">
Impact of a Data Breach or Outage
</Label>
</div>
<div className="flex gap-2">
<button className="rounded-full bg-gray-100 px-4 py-1 text-sm text-gray-900">
<button
onClick={() => setSelectedCriticality('LOW')}
className={cn(
"rounded-full px-4 py-1 text-sm transition-colors",
selectedCriticality === 'LOW'
? "bg-green-100 text-green-900 ring-2 ring-green-600 ring-offset-2"
: "bg-gray-100 text-gray-900 hover:bg-gray-200"
)}
>
Low
</button>
<button className="rounded-full bg-gray-100 px-4 py-1 text-sm text-gray-900">
<button
onClick={() => setSelectedCriticality('MEDIUM')}
className={cn(
"rounded-full px-4 py-1 text-sm transition-colors",
selectedCriticality === 'MEDIUM'
? "bg-yellow-100 text-yellow-900 ring-2 ring-yellow-600 ring-offset-2"
: "bg-gray-100 text-gray-900 hover:bg-gray-200"
)}
>
Medium
</button>
<button className="rounded-full bg-green-100 px-4 py-1 text-sm text-green-900">
<button
onClick={() => setSelectedCriticality('HIGH')}
className={cn(
"rounded-full px-4 py-1 text-sm transition-colors",
selectedCriticality === 'HIGH'
? "bg-red-100 text-red-900 ring-2 ring-red-600 ring-offset-2"
: "bg-gray-100 text-gray-900 hover:bg-gray-200"
)}
>
High
</button>
</div>
<p className="text-sm text-gray-500">
{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"}
</p>
</div>
</div>
</div>
</Card>
<Card className="p-6">
<div className="space-y-4">
<div className="space-y-2">
<h2 className="text-lg font-medium">Risk level</h2>
<p className="text-sm text-gray-500">
Based on the responses, the suggested risk level is medium.
</p>
</div>
<div className="flex gap-2">
<button className="rounded-full bg-gray-100 px-4 py-1 text-sm text-gray-900">
Low
</button>
<button className="rounded-full bg-gray-100 px-4 py-1 text-sm text-gray-900">
Medium
</button>
<button className="rounded-full bg-green-100 px-4 py-1 text-sm text-green-900">
High
</button>
<div className="space-y-2">
<div className="flex items-center gap-2">
<HelpCircle className="h-4 w-4 text-gray-400" />
<Label className="text-sm">Risk Tier</Label>
</div>
<div className="flex gap-2">
<button
onClick={() => setSelectedRiskTier('CRITICAL')}
className={cn(
"rounded-full px-4 py-1 text-sm transition-colors",
selectedRiskTier === 'CRITICAL'
? "bg-red-100 text-red-900 ring-2 ring-red-600 ring-offset-2"
: "bg-gray-100 text-gray-900 hover:bg-gray-200"
)}
>
Critical
</button>
<button
onClick={() => setSelectedRiskTier('SIGNIFICANT')}
className={cn(
"rounded-full px-4 py-1 text-sm transition-colors",
selectedRiskTier === 'SIGNIFICANT'
? "bg-yellow-100 text-yellow-900 ring-2 ring-yellow-600 ring-offset-2"
: "bg-gray-100 text-gray-900 hover:bg-gray-200"
)}
>
Significant
</button>
<button
onClick={() => setSelectedRiskTier('GENERAL')}
className={cn(
"rounded-full px-4 py-1 text-sm transition-colors",
selectedRiskTier === 'GENERAL'
? "bg-green-100 text-green-900 ring-2 ring-green-600 ring-offset-2"
: "bg-gray-100 text-gray-900 hover:bg-gray-200"
)}
>
General
</button>
</div>
<p className="text-sm text-gray-500">
{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"}
</p>
</div>
{data.node?.statusPageUrl && (
<div className="space-y-2">
<div className="flex items-center gap-2">
<HelpCircle className="h-4 w-4 text-gray-400" />
<Label className="text-sm">Status Page</Label>
</div>
<a
href={data.node.statusPageUrl}
className="text-primary hover:underline inline-flex items-center gap-1"
target="_blank"
rel="noopener noreferrer"
>
View Status Page
<ArrowUpRight className="h-4 w-4" />
</a>
</div>
)}
</div>
</div>
</Card>
@@ -296,7 +225,7 @@ function VendorOverviewPageFallback() {
<div className="h-4 w-96 bg-muted animate-pulse rounded" />
</div>
<div className="space-y-2">
{[1, 2, 3].map((i) => (
{[1, 2].map((i) => (
<div key={i} className="h-20 bg-muted animate-pulse rounded-lg" />
))}
</div>
@@ -307,7 +236,7 @@ function VendorOverviewPageFallback() {
export default function VendorOverviewPage() {
const { vendorId } = useParams();
const [queryRef, loadQuery] = useQueryLoader<VendorOverviewPageQueryType>(
VendorOverviewPageQuery,
vendorOverviewPageQuery,
);
useEffect(() => {

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<a9ec0002895dd62d2845ae4fd6df3a53>>
* @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;

View File

@@ -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
}

View File

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

View File

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

View File

@@ -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,
}
}

View File

@@ -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;

View File

@@ -0,0 +1,66 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// 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
}

View File

@@ -0,0 +1,66 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// 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
}

View File

@@ -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 {