diff --git a/apps/console/src/pages/organizations/vendors/VendorView.tsx b/apps/console/src/pages/organizations/vendors/VendorView.tsx
index 6650ffa2e..cb082a0de 100644
--- a/apps/console/src/pages/organizations/vendors/VendorView.tsx
+++ b/apps/console/src/pages/organizations/vendors/VendorView.tsx
@@ -20,6 +20,7 @@ import type { VendorViewDeleteComplianceReportMutation as DeleteComplianceReport
import type { VendorViewUploadComplianceReportMutation as UploadComplianceReportMutationType } from "./__generated__/VendorViewUploadComplianceReportMutation.graphql";
import type { VendorViewUpdateVendorMutation } from "./__generated__/VendorViewUpdateVendorMutation.graphql";
import type { VendorViewCreateRiskAssessmentMutation } from "./__generated__/VendorViewCreateRiskAssessmentMutation.graphql";
+import type { VendorViewAssessVendorMutation } from "./__generated__/VendorViewAssessVendorMutation.graphql";
import type {
BusinessImpact,
DataSensitivity,
@@ -31,6 +32,7 @@ import { PageTemplate } from "@/components/PageTemplate";
import { VendorViewSkeleton } from "./VendorPage";
import PeopleSelector from "@/components/PeopleSelector";
import { formatDistanceToNow, format, isPast } from "date-fns";
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
const vendorViewQuery = graphql`
query VendorViewQuery($vendorId: ID!, $organizationId: ID!) {
@@ -196,6 +198,38 @@ const createRiskAssessmentMutation = graphql`
}
`;
+const assessVendorMutation = graphql`
+ mutation VendorViewAssessVendorMutation($input: AssessVendorInput!) {
+ assessVendor(input: $input) {
+ vendor {
+ id
+ name
+ description
+ statusPageUrl
+ termsOfServiceUrl
+ privacyPolicyUrl
+ serviceLevelAgreementUrl
+ dataProcessingAgreementUrl
+ securityPageUrl
+ trustPageUrl
+ certifications
+ headquarterAddress
+ legalName
+ websiteUrl
+ businessOwner {
+ id
+ fullName
+ }
+ securityOwner {
+ id
+ fullName
+ }
+ updatedAt
+ }
+ }
+ }
+`;
+
// Format date for input field (YYYY-MM-DDTHH:mm)
function formatDateForInput(date: string | null | undefined): string {
if (!date) return "";
@@ -1070,9 +1104,9 @@ function RiskAssessmentDetailsModal({
Risk Assessment Details
-
-
+
Valid Until
@@ -1140,7 +1174,7 @@ function RiskAssessmentDetailsModal({
)}
-
+ }
+ >
+
+ {/* Assessement From Website Modal */}
+
);
}
export default function VendorView() {
const { vendorId, organizationId } = useParams();
+
const [queryRef, loadQuery] =
useQueryLoader
(vendorViewQuery);
diff --git a/apps/console/src/pages/organizations/vendors/__generated__/VendorViewAssessVendorMutation.graphql.ts b/apps/console/src/pages/organizations/vendors/__generated__/VendorViewAssessVendorMutation.graphql.ts
new file mode 100644
index 000000000..f43775b69
--- /dev/null
+++ b/apps/console/src/pages/organizations/vendors/__generated__/VendorViewAssessVendorMutation.graphql.ts
@@ -0,0 +1,257 @@
+/**
+ * @generated SignedSource<<71c7ed1be8cf0477074d6cf130143626>>
+ * @lightSyntaxTransform
+ * @nogrep
+ */
+
+/* tslint:disable */
+/* eslint-disable */
+// @ts-nocheck
+
+import { ConcreteRequest } from 'relay-runtime';
+export type AssessVendorInput = {
+ id: string;
+ websiteUrl: string;
+};
+export type VendorViewAssessVendorMutation$variables = {
+ input: AssessVendorInput;
+};
+export type VendorViewAssessVendorMutation$data = {
+ readonly assessVendor: {
+ readonly vendor: {
+ readonly businessOwner: {
+ readonly fullName: string;
+ readonly id: string;
+ } | null | undefined;
+ readonly certifications: ReadonlyArray;
+ readonly dataProcessingAgreementUrl: string | null | undefined;
+ readonly description: string | null | undefined;
+ readonly headquarterAddress: string | null | undefined;
+ readonly id: string;
+ readonly legalName: string | null | undefined;
+ readonly name: string;
+ readonly privacyPolicyUrl: string | null | undefined;
+ readonly securityOwner: {
+ readonly fullName: string;
+ readonly id: string;
+ } | null | undefined;
+ readonly securityPageUrl: string | null | undefined;
+ readonly serviceLevelAgreementUrl: string | null | undefined;
+ readonly statusPageUrl: string | null | undefined;
+ readonly termsOfServiceUrl: string | null | undefined;
+ readonly trustPageUrl: string | null | undefined;
+ readonly updatedAt: string;
+ readonly websiteUrl: string | null | undefined;
+ };
+ };
+};
+export type VendorViewAssessVendorMutation = {
+ response: VendorViewAssessVendorMutation$data;
+ variables: VendorViewAssessVendorMutation$variables;
+};
+
+const node: ConcreteRequest = (function(){
+var v0 = [
+ {
+ "defaultValue": null,
+ "kind": "LocalArgument",
+ "name": "input"
+ }
+],
+v1 = {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "id",
+ "storageKey": null
+},
+v2 = [
+ (v1/*: any*/),
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "fullName",
+ "storageKey": null
+ }
+],
+v3 = [
+ {
+ "alias": null,
+ "args": [
+ {
+ "kind": "Variable",
+ "name": "input",
+ "variableName": "input"
+ }
+ ],
+ "concreteType": "AssessVendorPayload",
+ "kind": "LinkedField",
+ "name": "assessVendor",
+ "plural": false,
+ "selections": [
+ {
+ "alias": null,
+ "args": null,
+ "concreteType": "Vendor",
+ "kind": "LinkedField",
+ "name": "vendor",
+ "plural": false,
+ "selections": [
+ (v1/*: any*/),
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "name",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "description",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "statusPageUrl",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "termsOfServiceUrl",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "privacyPolicyUrl",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "serviceLevelAgreementUrl",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "dataProcessingAgreementUrl",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "securityPageUrl",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "trustPageUrl",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "certifications",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "headquarterAddress",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "legalName",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "websiteUrl",
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "concreteType": "People",
+ "kind": "LinkedField",
+ "name": "businessOwner",
+ "plural": false,
+ "selections": (v2/*: any*/),
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "concreteType": "People",
+ "kind": "LinkedField",
+ "name": "securityOwner",
+ "plural": false,
+ "selections": (v2/*: any*/),
+ "storageKey": null
+ },
+ {
+ "alias": null,
+ "args": null,
+ "kind": "ScalarField",
+ "name": "updatedAt",
+ "storageKey": null
+ }
+ ],
+ "storageKey": null
+ }
+ ],
+ "storageKey": null
+ }
+];
+return {
+ "fragment": {
+ "argumentDefinitions": (v0/*: any*/),
+ "kind": "Fragment",
+ "metadata": null,
+ "name": "VendorViewAssessVendorMutation",
+ "selections": (v3/*: any*/),
+ "type": "Mutation",
+ "abstractKey": null
+ },
+ "kind": "Request",
+ "operation": {
+ "argumentDefinitions": (v0/*: any*/),
+ "kind": "Operation",
+ "name": "VendorViewAssessVendorMutation",
+ "selections": (v3/*: any*/)
+ },
+ "params": {
+ "cacheID": "ccc16b2de8f7f3936458cd4823a14992",
+ "id": null,
+ "metadata": {},
+ "name": "VendorViewAssessVendorMutation",
+ "operationKind": "mutation",
+ "text": "mutation VendorViewAssessVendorMutation(\n $input: AssessVendorInput!\n) {\n assessVendor(input: $input) {\n vendor {\n id\n name\n description\n statusPageUrl\n termsOfServiceUrl\n privacyPolicyUrl\n serviceLevelAgreementUrl\n dataProcessingAgreementUrl\n securityPageUrl\n trustPageUrl\n certifications\n headquarterAddress\n legalName\n websiteUrl\n businessOwner {\n id\n fullName\n }\n securityOwner {\n id\n fullName\n }\n updatedAt\n }\n }\n}\n"
+ }
+};
+})();
+
+(node as any).hash = "ed23d00ce5a44aa6bfd43a072552ed1c";
+
+export default node;
diff --git a/cfg/dev.yaml b/cfg/dev.yaml
index ae2f586de..6cb96885e 100644
--- a/cfg/dev.yaml
+++ b/cfg/dev.yaml
@@ -6,3 +6,7 @@ probod:
access-key-id: "probod"
secret-access-key: "thisisnotasecret"
endpoint: "http://127.0.0.1:9000"
+ openai:
+ api-key: "thisisnotasecret"
+ temperature: 0.1
+ model-name: "gpt-4o"
diff --git a/go.mod b/go.mod
index 289e0e1f7..a3e173136 100644
--- a/go.mod
+++ b/go.mod
@@ -16,6 +16,7 @@ require (
github.com/go-chi/cors v1.2.1
github.com/jackc/pgx/v5 v5.7.4
github.com/jhillyerd/enmime v1.3.0
+ github.com/openai/openai-go v1.1.0
github.com/prometheus/client_golang v1.22.0
github.com/vektah/gqlparser/v2 v2.5.27
go.gearno.de/crypto/uuid v0.1.0
@@ -64,6 +65,10 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sosodev/duration v1.3.1 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
+ github.com/tidwall/gjson v1.18.0 // indirect
+ github.com/tidwall/match v1.1.1 // indirect
+ github.com/tidwall/pretty v1.2.1 // indirect
+ github.com/tidwall/sjson v1.2.5 // indirect
github.com/urfave/cli/v2 v2.27.6 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
go.gearno.de/x/panicf v0.1.1 // indirect
diff --git a/go.sum b/go.sum
index 8bd7debc2..ebaddc1a7 100644
--- a/go.sum
+++ b/go.sum
@@ -108,6 +108,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
+github.com/openai/openai-go v1.1.0 h1:daSn+y+3QJUmLV1xfh7B8QtgJYRw1hg3yWxKtQDfROE=
+github.com/openai/openai-go v1.1.0/go.mod h1:g461MYGXEXBVdV5SaR/5tNzNbSfwTBBefwc+LlDCK0Y=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -140,6 +142,16 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
+github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
+github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
+github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTdwFp0s=
diff --git a/pkg/agents/vendor_assessment.go b/pkg/agents/vendor_assessment.go
new file mode 100644
index 000000000..08d07b430
--- /dev/null
+++ b/pkg/agents/vendor_assessment.go
@@ -0,0 +1,137 @@
+// 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 agents
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+
+ "github.com/openai/openai-go"
+ "github.com/openai/openai-go/option"
+ "github.com/openai/openai-go/packages/param"
+ "go.gearno.de/kit/log"
+)
+
+type (
+ VendorAssessment struct {
+ l *log.Logger
+ cfg Config
+ client *openai.Client
+ }
+
+ Config struct {
+ OpenAIAPIKey string
+ Temperature float64
+ ModelName string
+ }
+
+ vendorInfo struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Category string `json:"category"`
+ HeadquarterAddress string `json:"headquarter_address"`
+ LegalName string `json:"legal_name"`
+ PrivacyPolicyURL string `json:"privacy_policy_url"`
+ ServiceLevelAgreementURL string `json:"service_level_agreement_url"`
+ DataProcessingAgreementURL string `json:"data_processing_agreement_url"`
+ SecurityPageURL string `json:"security_page_url"`
+ TrustPageURL string `json:"trust_page_url"`
+ TermsOfServiceURL string `json:"terms_of_service_url"`
+ StatusPageURL string `json:"status_page_url"`
+ Certifications []string `json:"certifications"`
+ }
+)
+
+const (
+ systemPrompt = `
+ # Role: You are a compliance assistant.
+
+ # Objective
+ Your task is to fetch the provided company URL and to return comprehensive company information.
+
+ # For the company url, return the following fields in structured JSON format:
+ - name: The company's commonly used name
+ - description: One-sentence summary of the company's core offering
+ - headquarter_address: Company's main headquarter full address
+ - legal_name: Official registered company name
+ - privacy_policy_url: URL to privacy policy page
+ - service_level_agreement_url: URL to SLA page
+ - data_processing_agreement_url: URL to DPA page
+ - security_page_url: URL to security information page
+ - trust_page_url: URL to trust/compliance page
+ - terms_of_service_url: URL to terms of service page
+ - status_page_url: URL to system status page
+ - certifications: Array of security/compliance certifications (e.g., ["SOC2", "ISO27001"])
+
+ # SOP
+ - Please ensure the output is clean, standardized JSON.
+ - Use web search to gather info, if you cannot find what you are looking for, just return an empty string instead
+ - For URLs, return the full URL if found, otherwise an empty string
+ - For certifications, return an empty array if none found
+
+ # **Example output format:**
+ Respond ONLY with a JSON object. No explanation, no markdown, no preamble. Like this:
+ {
+ "name": "Stripe",
+ "description": "Online payment processing platform that enables businesses to accept and manage digital payments, supporting various payment methods and currencies with integrated fraud protection and compliance features",
+ "headquarter_address": "San Francisco, CA",
+ "legal_name": "Stripe, Inc.",
+ "privacy_policy_url": "https://stripe.com/privacy",
+ "service_level_agreement_url": "https://stripe.com/sla",
+ "data_processing_agreement_url": "https://stripe.com/dpa",
+ "security_page_url": "https://stripe.com/security",
+ "trust_page_url": "https://stripe.com/trust",
+ "terms_of_service_url": "https://stripe.com/terms",
+ "status_page_url": "https://status.stripe.com",
+ "certifications": ["SOC1", "SOC2", "PCI DSS Level 1", "ISO 27001"]
+ }
+
+ ### Company url:
+ `
+)
+
+func NewVendorAssessment(l *log.Logger, cfg Config) *VendorAssessment {
+ client := openai.NewClient(option.WithAPIKey(cfg.OpenAIAPIKey))
+
+ return &VendorAssessment{l: l, cfg: cfg, client: &client}
+}
+
+func (va *VendorAssessment) Fetch(ctx context.Context, websiteURL string) (*vendorInfo, error) {
+ model := openai.ChatModel(va.cfg.ModelName)
+ chatCompletion, err := va.client.Chat.Completions.New(ctx, openai.ChatCompletionNewParams{
+ Messages: []openai.ChatCompletionMessageParamUnion{
+ openai.SystemMessage(systemPrompt),
+ openai.UserMessage(websiteURL),
+ },
+ Model: model,
+ Temperature: param.NewOpt(va.cfg.Temperature),
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse vendor info: %w", err)
+ }
+
+ if len(chatCompletion.Choices) == 0 {
+ return nil, fmt.Errorf("no completion choices returned from API")
+ }
+
+ var vendorInfo vendorInfo
+ err = json.Unmarshal([]byte(chatCompletion.Choices[0].Message.Content), &vendorInfo)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse vendor info: %w", err)
+ }
+
+ return &vendorInfo, nil
+}
diff --git a/pkg/probo/service.go b/pkg/probo/service.go
index aa3f209af..5d36ac5c3 100644
--- a/pkg/probo/service.go
+++ b/pkg/probo/service.go
@@ -19,6 +19,7 @@ import (
"fmt"
"github.com/aws/aws-sdk-go-v2/service/s3"
+ "github.com/getprobo/probo/pkg/agents"
"github.com/getprobo/probo/pkg/coredata"
"github.com/getprobo/probo/pkg/crypto/cipher"
"github.com/getprobo/probo/pkg/filevalidation"
@@ -28,12 +29,13 @@ import (
type (
Service struct {
- pg *pg.Client
- s3 *s3.Client
- bucket string
- encryptionKey cipher.EncryptionKey
- hostname string
- tokenSecret string
+ pg *pg.Client
+ s3 *s3.Client
+ bucket string
+ encryptionKey cipher.EncryptionKey
+ hostname string
+ tokenSecret string
+ vendorAssessment agents.Config
}
TenantService struct {
@@ -44,6 +46,7 @@ type (
scope coredata.Scoper
hostname string
tokenSecret string
+ vendorAssessment *agents.VendorAssessment
Frameworks *FrameworkService
Measures *MeasureService
Tasks *TaskService
@@ -67,18 +70,20 @@ func NewService(
bucket string,
hostname string,
tokenSecret string,
+ vendorAssessment agents.Config,
) (*Service, error) {
if bucket == "" {
return nil, fmt.Errorf("bucket is required")
}
svc := &Service{
- pg: pgClient,
- s3: s3Client,
- bucket: bucket,
- encryptionKey: encryptionKey,
- hostname: hostname,
- tokenSecret: tokenSecret,
+ pg: pgClient,
+ s3: s3Client,
+ bucket: bucket,
+ encryptionKey: encryptionKey,
+ hostname: hostname,
+ tokenSecret: tokenSecret,
+ vendorAssessment: vendorAssessment,
}
return svc, nil
@@ -86,13 +91,14 @@ func NewService(
func (s *Service) WithTenant(tenantID gid.TenantID) *TenantService {
tenantService := &TenantService{
- pg: s.pg,
- s3: s.s3,
- bucket: s.bucket,
- encryptionKey: s.encryptionKey,
- hostname: s.hostname,
- scope: coredata.NewScope(tenantID),
- tokenSecret: s.tokenSecret,
+ pg: s.pg,
+ s3: s.s3,
+ bucket: s.bucket,
+ encryptionKey: s.encryptionKey,
+ hostname: s.hostname,
+ scope: coredata.NewScope(tenantID),
+ tokenSecret: s.tokenSecret,
+ vendorAssessment: agents.NewVendorAssessment(nil, s.vendorAssessment),
}
tenantService.Frameworks = &FrameworkService{svc: tenantService}
diff --git a/pkg/probo/vendor_service.go b/pkg/probo/vendor_service.go
index 657d4c142..290b881c4 100644
--- a/pkg/probo/vendor_service.go
+++ b/pkg/probo/vendor_service.go
@@ -70,6 +70,11 @@ type (
SecurityOwnerID *gid.GID
}
+ AssessVendorRequest struct {
+ ID gid.GID
+ WebsiteURL string
+ }
+
CreateVendorRiskAssessmentRequest struct {
VendorID gid.GID
AssessedByID gid.GID
@@ -421,3 +426,34 @@ func (s VendorService) GetRiskAssessment(
return vendorRiskAssessment, nil
}
+
+func (s VendorService) Assess(
+ ctx context.Context,
+ req AssessVendorRequest,
+) (*coredata.Vendor, error) {
+ vendorInfo, err := s.svc.vendorAssessment.Fetch(ctx, req.WebsiteURL)
+ if err != nil {
+ return nil, fmt.Errorf("failed to assess vendor info: %w", err)
+ }
+
+ vendor := &coredata.Vendor{
+ ID: req.ID,
+ Name: vendorInfo.Name,
+ WebsiteURL: &req.WebsiteURL,
+ Description: &vendorInfo.Description,
+ Category: vendorInfo.Category,
+ HeadquarterAddress: &vendorInfo.HeadquarterAddress,
+ LegalName: &vendorInfo.LegalName,
+ PrivacyPolicyURL: &vendorInfo.PrivacyPolicyURL,
+ ServiceLevelAgreementURL: &vendorInfo.ServiceLevelAgreementURL,
+ DataProcessingAgreementURL: &vendorInfo.DataProcessingAgreementURL,
+ SecurityPageURL: &vendorInfo.SecurityPageURL,
+ TrustPageURL: &vendorInfo.TrustPageURL,
+ TermsOfServiceURL: &vendorInfo.TermsOfServiceURL,
+ StatusPageURL: &vendorInfo.StatusPageURL,
+ Certifications: vendorInfo.Certifications,
+ UpdatedAt: time.Now(),
+ }
+
+ return vendor, nil
+}
diff --git a/pkg/probod/openai_config.go b/pkg/probod/openai_config.go
new file mode 100644
index 000000000..0acea872c
--- /dev/null
+++ b/pkg/probod/openai_config.go
@@ -0,0 +1,21 @@
+// 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 probod
+
+type openaiConfig struct {
+ APIKey string `json:"api-key"`
+ Temperature float64 `json:"temperature"`
+ ModelName string `json:"model-name"`
+}
diff --git a/pkg/probod/probod.go b/pkg/probod/probod.go
index 0e1a098c5..29610f356 100644
--- a/pkg/probod/probod.go
+++ b/pkg/probod/probod.go
@@ -24,6 +24,7 @@ import (
"time"
"github.com/aws/aws-sdk-go-v2/service/s3"
+ "github.com/getprobo/probo/pkg/agents"
"github.com/getprobo/probo/pkg/awsconfig"
"github.com/getprobo/probo/pkg/connector"
"github.com/getprobo/probo/pkg/coredata"
@@ -59,6 +60,7 @@ type (
AWS awsConfig `json:"aws"`
Mailer mailerConfig `json:"mailer"`
Connectors []connectorConfig `json:"connectors"`
+ OpenAI openaiConfig `json:"openai"`
}
)
@@ -185,6 +187,14 @@ func (impl *Implm) Run(
}
}
+ vendorAssessmentConfig := agents.Config{
+ OpenAIAPIKey: impl.cfg.OpenAI.APIKey,
+ Temperature: impl.cfg.OpenAI.Temperature,
+ ModelName: impl.cfg.OpenAI.ModelName,
+ }
+
+ vendorAssessment := agents.NewVendorAssessment(l.Named("vendor-assessment"), vendorAssessmentConfig)
+
usrmgrService, err := usrmgr.NewService(
ctx,
pgClient,
@@ -205,6 +215,7 @@ func (impl *Implm) Run(
impl.cfg.AWS.Bucket,
impl.cfg.Hostname,
impl.cfg.Auth.Cookie.Secret,
+ vendorAssessmentConfig,
)
if err != nil {
return fmt.Errorf("cannot create probo service: %w", err)
@@ -216,6 +227,7 @@ func (impl *Implm) Run(
Probo: proboService,
Usrmgr: usrmgrService,
ConnectorRegistry: defaultConnectorRegistry,
+ VendorAssessment: vendorAssessment,
SafeRedirect: &saferedirect.SafeRedirect{AllowedHost: impl.cfg.Hostname},
Logger: l.Named("http.server"),
Auth: console_v1.AuthConfig{
diff --git a/pkg/server/api/console/v1/schema.graphql b/pkg/server/api/console/v1/schema.graphql
index 29ccc1810..8e6273276 100644
--- a/pkg/server/api/console/v1/schema.graphql
+++ b/pkg/server/api/console/v1/schema.graphql
@@ -1088,6 +1088,8 @@ type Mutation {
): CreateVendorRiskAssessmentPayload!
exportAudit(input: ExportAuditInput!): ExportAuditPayload!
+
+ assessVendor(input: AssessVendorInput!): AssessVendorPayload!
}
# Input Types
@@ -1779,3 +1781,12 @@ input ExportAuditInput {
type ExportAuditPayload {
url: String!
}
+
+input AssessVendorInput {
+ id: ID!
+ websiteUrl: String!
+}
+
+type AssessVendorPayload {
+ vendor: Vendor!
+}
diff --git a/pkg/server/api/console/v1/schema/schema.go b/pkg/server/api/console/v1/schema/schema.go
index 66f47b73c..c226f0782 100644
--- a/pkg/server/api/console/v1/schema/schema.go
+++ b/pkg/server/api/console/v1/schema/schema.go
@@ -65,6 +65,10 @@ type DirectiveRoot struct {
}
type ComplexityRoot struct {
+ AssessVendorPayload struct {
+ Vendor func(childComplexity int) int
+ }
+
AssignTaskPayload struct {
Task func(childComplexity int) int
}
@@ -329,6 +333,7 @@ type ComplexityRoot struct {
}
Mutation struct {
+ AssessVendor func(childComplexity int, input types.AssessVendorInput) int
AssignTask func(childComplexity int, input types.AssignTaskInput) int
ConfirmEmail func(childComplexity int, input types.ConfirmEmailInput) int
CreateControlMeasureMapping func(childComplexity int, input types.CreateControlMeasureMappingInput) int
@@ -827,6 +832,7 @@ type MutationResolver interface {
SendSigningNotifications(ctx context.Context, input types.SendSigningNotificationsInput) (*types.SendSigningNotificationsPayload, error)
CreateVendorRiskAssessment(ctx context.Context, input types.CreateVendorRiskAssessmentInput) (*types.CreateVendorRiskAssessmentPayload, error)
ExportAudit(ctx context.Context, input types.ExportAuditInput) (*types.ExportAuditPayload, error)
+ AssessVendor(ctx context.Context, input types.AssessVendorInput) (*types.AssessVendorPayload, error)
}
type OrganizationResolver interface {
LogoURL(ctx context.Context, obj *types.Organization) (*string, error)
@@ -919,6 +925,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
_ = ec
switch typeName + "." + field {
+ case "AssessVendorPayload.vendor":
+ if e.complexity.AssessVendorPayload.Vendor == nil {
+ break
+ }
+
+ return e.complexity.AssessVendorPayload.Vendor(childComplexity), true
+
case "AssignTaskPayload.task":
if e.complexity.AssignTaskPayload.Task == nil {
break
@@ -1724,6 +1737,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.MeasureEdge.Node(childComplexity), true
+ case "Mutation.assessVendor":
+ if e.complexity.Mutation.AssessVendor == nil {
+ break
+ }
+
+ args, err := ec.field_Mutation_assessVendor_args(ctx, rawArgs)
+ if err != nil {
+ return 0, false
+ }
+
+ return e.complexity.Mutation.AssessVendor(childComplexity, args["input"].(types.AssessVendorInput)), true
+
case "Mutation.assignTask":
if e.complexity.Mutation.AssignTask == nil {
break
@@ -3889,6 +3914,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
opCtx := graphql.GetOperationContext(ctx)
ec := executionContext{opCtx, e, 0, 0, make(chan graphql.DeferredResult)}
inputUnmarshalMap := graphql.BuildUnmarshalerMap(
+ ec.unmarshalInputAssessVendorInput,
ec.unmarshalInputAssignTaskInput,
ec.unmarshalInputConfirmEmailInput,
ec.unmarshalInputConnectorOrder,
@@ -5147,6 +5173,8 @@ type Mutation {
): CreateVendorRiskAssessmentPayload!
exportAudit(input: ExportAuditInput!): ExportAuditPayload!
+
+ assessVendor(input: AssessVendorInput!): AssessVendorPayload!
}
# Input Types
@@ -5838,6 +5866,15 @@ input ExportAuditInput {
type ExportAuditPayload {
url: String!
}
+
+input AssessVendorInput {
+ id: ID!
+ websiteUrl: String!
+}
+
+type AssessVendorPayload {
+ vendor: Vendor!
+}
`, BuiltIn: false},
}
var parsedSchema = gqlparser.MustLoadSchema(sources...)
@@ -6511,6 +6548,29 @@ func (ec *executionContext) field_Measure_tasks_argsOrderBy(
return zeroVal, nil
}
+func (ec *executionContext) field_Mutation_assessVendor_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
+ var err error
+ args := map[string]any{}
+ arg0, err := ec.field_Mutation_assessVendor_argsInput(ctx, rawArgs)
+ if err != nil {
+ return nil, err
+ }
+ args["input"] = arg0
+ return args, nil
+}
+func (ec *executionContext) field_Mutation_assessVendor_argsInput(
+ ctx context.Context,
+ rawArgs map[string]any,
+) (types.AssessVendorInput, error) {
+ ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input"))
+ if tmp, ok := rawArgs["input"]; ok {
+ return ec.unmarshalNAssessVendorInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐAssessVendorInput(ctx, tmp)
+ }
+
+ var zeroVal types.AssessVendorInput
+ return zeroVal, nil
+}
+
func (ec *executionContext) field_Mutation_assignTask_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
@@ -9699,6 +9759,94 @@ func (ec *executionContext) field___Type_fields_argsIncludeDeprecated(
// region **************************** field.gotpl *****************************
+func (ec *executionContext) _AssessVendorPayload_vendor(ctx context.Context, field graphql.CollectedField, obj *types.AssessVendorPayload) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_AssessVendorPayload_vendor(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ ret = graphql.Null
+ }
+ }()
+ resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
+ ctx = rctx // use context from middleware stack in children
+ return obj.Vendor, 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.(*types.Vendor)
+ fc.Result = res
+ return ec.marshalNVendor2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐVendor(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_AssessVendorPayload_vendor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "AssessVendorPayload",
+ Field: field,
+ IsMethod: false,
+ IsResolver: false,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ switch field.Name {
+ case "id":
+ return ec.fieldContext_Vendor_id(ctx, field)
+ case "name":
+ return ec.fieldContext_Vendor_name(ctx, field)
+ case "description":
+ return ec.fieldContext_Vendor_description(ctx, field)
+ case "organization":
+ return ec.fieldContext_Vendor_organization(ctx, field)
+ case "complianceReports":
+ return ec.fieldContext_Vendor_complianceReports(ctx, field)
+ case "riskAssessments":
+ return ec.fieldContext_Vendor_riskAssessments(ctx, field)
+ case "businessOwner":
+ return ec.fieldContext_Vendor_businessOwner(ctx, field)
+ case "securityOwner":
+ return ec.fieldContext_Vendor_securityOwner(ctx, field)
+ case "statusPageUrl":
+ return ec.fieldContext_Vendor_statusPageUrl(ctx, field)
+ case "termsOfServiceUrl":
+ return ec.fieldContext_Vendor_termsOfServiceUrl(ctx, field)
+ case "privacyPolicyUrl":
+ return ec.fieldContext_Vendor_privacyPolicyUrl(ctx, field)
+ case "serviceLevelAgreementUrl":
+ return ec.fieldContext_Vendor_serviceLevelAgreementUrl(ctx, field)
+ case "dataProcessingAgreementUrl":
+ return ec.fieldContext_Vendor_dataProcessingAgreementUrl(ctx, field)
+ case "certifications":
+ return ec.fieldContext_Vendor_certifications(ctx, field)
+ case "securityPageUrl":
+ return ec.fieldContext_Vendor_securityPageUrl(ctx, field)
+ case "trustPageUrl":
+ return ec.fieldContext_Vendor_trustPageUrl(ctx, field)
+ case "headquarterAddress":
+ return ec.fieldContext_Vendor_headquarterAddress(ctx, field)
+ case "legalName":
+ return ec.fieldContext_Vendor_legalName(ctx, field)
+ case "websiteUrl":
+ return ec.fieldContext_Vendor_websiteUrl(ctx, field)
+ case "createdAt":
+ return ec.fieldContext_Vendor_createdAt(ctx, field)
+ case "updatedAt":
+ return ec.fieldContext_Vendor_updatedAt(ctx, field)
+ }
+ return nil, fmt.Errorf("no field named %q was found under type Vendor", field.Name)
+ },
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _AssignTaskPayload_task(ctx context.Context, field graphql.CollectedField, obj *types.AssignTaskPayload) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_AssignTaskPayload_task(ctx, field)
if err != nil {
@@ -18173,6 +18321,65 @@ func (ec *executionContext) fieldContext_Mutation_exportAudit(ctx context.Contex
return fc, nil
}
+func (ec *executionContext) _Mutation_assessVendor(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
+ fc, err := ec.fieldContext_Mutation_assessVendor(ctx, field)
+ if err != nil {
+ return graphql.Null
+ }
+ ctx = graphql.WithFieldContext(ctx, fc)
+ defer func() {
+ if r := recover(); r != nil {
+ ec.Error(ctx, ec.Recover(ctx, r))
+ ret = graphql.Null
+ }
+ }()
+ resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
+ ctx = rctx // use context from middleware stack in children
+ return ec.resolvers.Mutation().AssessVendor(rctx, fc.Args["input"].(types.AssessVendorInput))
+ })
+ 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.(*types.AssessVendorPayload)
+ fc.Result = res
+ return ec.marshalNAssessVendorPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐAssessVendorPayload(ctx, field.Selections, res)
+}
+
+func (ec *executionContext) fieldContext_Mutation_assessVendor(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
+ fc = &graphql.FieldContext{
+ Object: "Mutation",
+ Field: field,
+ IsMethod: true,
+ IsResolver: true,
+ Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
+ switch field.Name {
+ case "vendor":
+ return ec.fieldContext_AssessVendorPayload_vendor(ctx, field)
+ }
+ return nil, fmt.Errorf("no field named %q was found under type AssessVendorPayload", field.Name)
+ },
+ }
+ defer func() {
+ if r := recover(); r != nil {
+ err = ec.Recover(ctx, r)
+ ec.Error(ctx, err)
+ }
+ }()
+ ctx = graphql.WithFieldContext(ctx, fc)
+ if fc.Args, err = ec.field_Mutation_assessVendor_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
+ ec.Error(ctx, err)
+ return fc, err
+ }
+ return fc, nil
+}
+
func (ec *executionContext) _Organization_id(ctx context.Context, field graphql.CollectedField, obj *types.Organization) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Organization_id(ctx, field)
if err != nil {
@@ -30810,6 +31017,40 @@ func (ec *executionContext) fieldContext___Type_isOneOf(_ context.Context, field
// region **************************** input.gotpl *****************************
+func (ec *executionContext) unmarshalInputAssessVendorInput(ctx context.Context, obj any) (types.AssessVendorInput, error) {
+ var it types.AssessVendorInput
+ asMap := map[string]any{}
+ for k, v := range obj.(map[string]any) {
+ asMap[k] = v
+ }
+
+ fieldsInOrder := [...]string{"id", "websiteUrl"}
+ 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 "websiteUrl":
+ ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("websiteUrl"))
+ data, err := ec.unmarshalNString2string(ctx, v)
+ if err != nil {
+ return it, err
+ }
+ it.WebsiteURL = data
+ }
+ }
+
+ return it, nil
+}
+
func (ec *executionContext) unmarshalInputAssignTaskInput(ctx context.Context, obj any) (types.AssignTaskInput, error) {
var it types.AssignTaskInput
asMap := map[string]any{}
@@ -33899,6 +34140,45 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj
// region **************************** object.gotpl ****************************
+var assessVendorPayloadImplementors = []string{"AssessVendorPayload"}
+
+func (ec *executionContext) _AssessVendorPayload(ctx context.Context, sel ast.SelectionSet, obj *types.AssessVendorPayload) graphql.Marshaler {
+ fields := graphql.CollectFields(ec.OperationContext, sel, assessVendorPayloadImplementors)
+
+ out := graphql.NewFieldSet(fields)
+ deferred := make(map[string]*graphql.FieldSet)
+ for i, field := range fields {
+ switch field.Name {
+ case "__typename":
+ out.Values[i] = graphql.MarshalString("AssessVendorPayload")
+ case "vendor":
+ out.Values[i] = ec._AssessVendorPayload_vendor(ctx, field, obj)
+ if out.Values[i] == graphql.Null {
+ out.Invalids++
+ }
+ default:
+ panic("unknown field " + strconv.Quote(field.Name))
+ }
+ }
+ out.Dispatch(ctx)
+ if out.Invalids > 0 {
+ return graphql.Null
+ }
+
+ atomic.AddInt32(&ec.deferred, int32(len(deferred)))
+
+ for label, dfs := range deferred {
+ ec.processDeferredGroup(graphql.DeferredGroup{
+ Label: label,
+ Path: graphql.GetPath(ctx),
+ FieldSet: dfs,
+ Context: ctx,
+ })
+ }
+
+ return out
+}
+
var assignTaskPayloadImplementors = []string{"AssignTaskPayload"}
func (ec *executionContext) _AssignTaskPayload(ctx context.Context, sel ast.SelectionSet, obj *types.AssignTaskPayload) graphql.Marshaler {
@@ -36929,6 +37209,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null {
out.Invalids++
}
+ case "assessVendor":
+ out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
+ return ec._Mutation_assessVendor(ctx, field)
+ })
+ if out.Values[i] == graphql.Null {
+ out.Invalids++
+ }
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@@ -41377,6 +41664,25 @@ func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, o
// region ***************************** type.gotpl *****************************
+func (ec *executionContext) unmarshalNAssessVendorInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐAssessVendorInput(ctx context.Context, v any) (types.AssessVendorInput, error) {
+ res, err := ec.unmarshalInputAssessVendorInput(ctx, v)
+ return res, graphql.ErrorOnPath(ctx, err)
+}
+
+func (ec *executionContext) marshalNAssessVendorPayload2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐAssessVendorPayload(ctx context.Context, sel ast.SelectionSet, v types.AssessVendorPayload) graphql.Marshaler {
+ return ec._AssessVendorPayload(ctx, sel, &v)
+}
+
+func (ec *executionContext) marshalNAssessVendorPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐAssessVendorPayload(ctx context.Context, sel ast.SelectionSet, v *types.AssessVendorPayload) graphql.Marshaler {
+ if v == nil {
+ if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
+ ec.Errorf(ctx, "the requested element is null which the schema does not allow")
+ }
+ return graphql.Null
+ }
+ return ec._AssessVendorPayload(ctx, sel, v)
+}
+
func (ec *executionContext) unmarshalNAssignTaskInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐAssignTaskInput(ctx context.Context, v any) (types.AssignTaskInput, error) {
res, err := ec.unmarshalInputAssignTaskInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
diff --git a/pkg/server/api/console/v1/types/types.go b/pkg/server/api/console/v1/types/types.go
index 79f164cfb..63b4ba05a 100644
--- a/pkg/server/api/console/v1/types/types.go
+++ b/pkg/server/api/console/v1/types/types.go
@@ -20,6 +20,15 @@ type Node interface {
GetID() gid.GID
}
+type AssessVendorInput struct {
+ ID gid.GID `json:"id"`
+ WebsiteURL string `json:"websiteUrl"`
+}
+
+type AssessVendorPayload struct {
+ Vendor *Vendor `json:"vendor"`
+}
+
type AssignTaskInput struct {
TaskID gid.GID `json:"taskId"`
AssignedToID gid.GID `json:"assignedToId"`
diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go
index b02f329d1..bfe2e47da 100644
--- a/pkg/server/api/console/v1/v1_resolver.go
+++ b/pkg/server/api/console/v1/v1_resolver.go
@@ -1316,6 +1316,23 @@ func (r *mutationResolver) ExportAudit(ctx context.Context, input types.ExportAu
}, nil
}
+// AssessVendor is the resolver for the assessVendor field.
+func (r *mutationResolver) AssessVendor(ctx context.Context, input types.AssessVendorInput) (*types.AssessVendorPayload, error) {
+ svc := GetTenantService(ctx, r.proboSvc, input.ID.TenantID())
+
+ vendor, err := svc.Vendors.Assess(ctx, probo.AssessVendorRequest{
+ ID: input.ID,
+ WebsiteURL: input.WebsiteURL,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("cannot assess vendor: %w", err)
+ }
+
+ return &types.AssessVendorPayload{
+ Vendor: types.NewVendor(vendor),
+ }, nil
+}
+
// LogoURL is the resolver for the logoUrl field.
func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) {
svc := GetTenantService(ctx, r.proboSvc, obj.ID.TenantID())
diff --git a/pkg/server/server.go b/pkg/server/server.go
index dba756728..f3ef81d72 100644
--- a/pkg/server/server.go
+++ b/pkg/server/server.go
@@ -19,6 +19,7 @@ import (
"net/http"
"strings"
+ "github.com/getprobo/probo/pkg/agents"
"github.com/getprobo/probo/pkg/connector"
"github.com/getprobo/probo/pkg/probo"
"github.com/getprobo/probo/pkg/saferedirect"
@@ -37,6 +38,7 @@ type Config struct {
Usrmgr *usrmgr.Service
Auth console_v1.AuthConfig
ConnectorRegistry *connector.ConnectorRegistry
+ VendorAssessment *agents.VendorAssessment
SafeRedirect *saferedirect.SafeRedirect
Logger *log.Logger
}