Add vendor compliance reports UI

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-04-10 09:31:44 -07:00
parent 9b4b3cc222
commit c63e2756ba
7 changed files with 828 additions and 23 deletions

View File

@@ -6,6 +6,10 @@ All notable changes to this project will be documented in this file.
## [0.4.2] - 2025-04-09
### Added
- Add vendor compliance reports UI
### Changed
- Simplified policy data model by removing version field and optimistic concurrency

View File

@@ -12,9 +12,12 @@ import {
usePreloadedQuery,
useQueryLoader,
useMutation,
ConnectionHandler,
} from "react-relay";
import { Suspense, useEffect, useState, useCallback } from "react";
import { Suspense, useEffect, useState, useCallback, useRef } from "react";
import type { VendorViewQuery as VendorViewQueryType } from "./__generated__/VendorViewQuery.graphql";
import type { VendorViewDeleteComplianceReportMutation as DeleteComplianceReportMutationType } from "./__generated__/VendorViewDeleteComplianceReportMutation.graphql";
import type { VendorViewUploadComplianceReportMutation as UploadComplianceReportMutationType } from "./__generated__/VendorViewUploadComplianceReportMutation.graphql";
import { useParams } from "react-router";
import { cn } from "@/lib/utils";
import { PageTemplate } from "@/components/PageTemplate";
@@ -36,6 +39,20 @@ const vendorViewQuery = graphql`
privacyPolicyUrl
createdAt
updatedAt
complianceReports(first: 100)
@connection(key: "VendorView_complianceReports") {
edges {
node {
id
reportName
reportDate
validUntil
fileUrl
fileSize
createdAt
}
}
}
}
}
}
@@ -61,6 +78,38 @@ const updateVendorMutation = graphql`
}
`;
const deleteComplianceReportMutation = graphql`
mutation VendorViewDeleteComplianceReportMutation(
$input: DeleteVendorComplianceReportInput!
$connections: [ID!]!
) {
deleteVendorComplianceReport(input: $input) {
deletedVendorComplianceReportId @deleteEdge(connections: $connections)
}
}
`;
const uploadComplianceReportMutation = graphql`
mutation VendorViewUploadComplianceReportMutation(
$input: UploadVendorComplianceReportInput!
$connections: [ID!]!
) {
uploadVendorComplianceReport(input: $input) {
vendorComplianceReportEdge @appendEdge(connections: $connections) {
node {
id
reportName
reportDate
validUntil
fileUrl
fileSize
createdAt
}
}
}
}
`;
function EditableField({
label,
value,
@@ -105,6 +154,129 @@ function formatDateForAPI(dateStr: string): string {
return date.toISOString();
}
interface ComplianceReport {
id: string;
reportName: string;
reportDate: string;
validUntil: string | null | undefined;
fileUrl: string;
fileSize: number;
createdAt: string;
}
function ComplianceReportsTable({
reports,
onDelete,
onUpload,
}: {
reports: ComplianceReport[];
onDelete: (id: string) => void;
onUpload: (event: React.ChangeEvent<HTMLInputElement>) => void;
}) {
const fileInputRef = useRef<HTMLInputElement>(null);
// Truncate filename to a reasonable length if needed
const truncateFilename = (filename: string, maxLength = 40) => {
if (filename.length <= maxLength) return filename;
const extension = filename.split(".").pop();
const name = filename.substring(0, maxLength - extension!.length - 3);
return `${name}...${extension}`;
};
return (
<div className="space-y-4">
<div className="space-y-2">
<h2 className="text-lg font-medium">Compliance Reports</h2>
<p className="text-sm text-secondary">
Upload and manage compliance reports for this vendor
</p>
</div>
<div className="rounded-md border">
<div className="overflow-hidden">
<table className="w-full table-fixed">
<thead>
<tr className="border-b">
<th className="w-2/5 px-4 py-2 text-left text-sm font-medium">
Report Name
</th>
<th className="w-1/5 px-4 py-2 text-left text-sm font-medium">
Report Date
</th>
<th className="w-1/5 px-4 py-2 text-left text-sm font-medium">
Valid Until
</th>
<th className="w-1/5 px-4 py-2 text-left text-sm font-medium">
File Size
</th>
<th className="w-1/5 px-4 py-2 text-left text-sm font-medium">
Actions
</th>
</tr>
</thead>
<tbody>
{reports.map((report) => (
<tr key={report.id} className="border-b">
<td className="px-4 py-2">
<a
href={report.fileUrl}
target="_blank"
rel="noopener noreferrer"
className="block text-primary hover:underline"
title={report.reportName}
>
<div className="overflow-hidden text-ellipsis whitespace-nowrap">
{truncateFilename(report.reportName)}
</div>
</a>
</td>
<td className="px-4 py-2 text-sm">
{new Date(report.reportDate).toLocaleDateString()}
</td>
<td className="px-4 py-2 text-sm">
{report.validUntil
? new Date(report.validUntil).toLocaleDateString()
: "N/A"}
</td>
<td className="px-4 py-2 text-sm">
{(report.fileSize / 1024 / 1024).toFixed(2)} MB
</td>
<td className="px-4 py-2">
<Button
variant="destructive"
size="sm"
onClick={() => onDelete(report.id)}
>
Delete
</Button>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
<div className="mt-4">
<input
type="file"
ref={fileInputRef}
className="hidden"
onChange={onUpload}
accept=".pdf"
/>
<Button
onClick={() => fileInputRef.current?.click()}
className="bg-primary text-invert hover:bg-primary/90"
>
Upload New Report
</Button>
<p className="mt-2 text-sm text-secondary">
Only PDF files up to 10MB are allowed
</p>
</div>
</div>
);
}
function VendorViewContent({
queryRef,
}: {
@@ -115,7 +287,6 @@ function VendorViewContent({
const [formData, setFormData] = useState({
name: data.node.name || "",
description: data.node.description || "",
// Format dates properly for datetime-local input
serviceStartAt: formatDateForInput(data.node.serviceStartAt),
serviceTerminationAt: formatDateForInput(data.node.serviceTerminationAt),
serviceCriticality: data.node.serviceCriticality,
@@ -124,7 +295,15 @@ function VendorViewContent({
termsOfServiceUrl: data.node.termsOfServiceUrl || "",
privacyPolicyUrl: data.node.privacyPolicyUrl || "",
});
const [commit] = useMutation(updateVendorMutation);
const [updateVendor] = useMutation(updateVendorMutation);
const [deleteVendorComplianceReport] =
useMutation<DeleteComplianceReportMutationType>(
deleteComplianceReportMutation
);
const [uploadVendorComplianceReport] =
useMutation<UploadComplianceReportMutationType>(
uploadComplianceReportMutation
);
const [, loadQuery] = useQueryLoader<VendorViewQueryType>(vendorViewQuery);
const { toast } = useToast();
@@ -139,7 +318,7 @@ function VendorViewContent({
: null,
};
commit({
updateVendor({
variables: {
input: {
id: data.node.id,
@@ -173,7 +352,7 @@ function VendorViewContent({
}
},
});
}, [commit, data.node.id, formData, loadQuery, toast]);
}, [updateVendor, data.node.id, formData, loadQuery, toast]);
const handleFieldChange = (field: keyof typeof formData, value: unknown) => {
setFormData((prev) => ({
@@ -199,6 +378,110 @@ function VendorViewContent({
setEditedFields(new Set());
};
const handleDeleteReport = useCallback(
(reportId: string) => {
deleteVendorComplianceReport({
variables: {
connections: [
ConnectionHandler.getConnectionID(
data.node.id!,
"VendorView_complianceReports"
),
],
input: {
reportId,
},
},
onCompleted: () => {
toast({
title: "Success",
description: "Compliance report deleted successfully",
variant: "default",
});
loadQuery({ vendorId: data.node.id! });
},
onError: (error) => {
toast({
title: "Error",
description: error.message || "Failed to delete compliance report",
variant: "destructive",
});
},
});
},
[deleteVendorComplianceReport, data.node.id, loadQuery, toast]
);
const handleUploadReport = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
// Validate file type
if (file.type !== "application/pdf") {
toast({
title: "Error",
description: "Only PDF files are allowed",
variant: "destructive",
});
return;
}
// Validate file size (max 10MB)
if (file.size > 10 * 1024 * 1024) {
toast({
title: "Error",
description: "File size must be less than 10MB",
variant: "destructive",
});
return;
}
const reader = new FileReader();
reader.onload = () => {
const reportDate = new Date().toISOString();
uploadVendorComplianceReport({
variables: {
connections: [
ConnectionHandler.getConnectionID(
data.node.id!,
"VendorView_complianceReports"
),
],
input: {
vendorId: data.node.id!,
reportDate,
reportName: file.name,
file: null,
},
},
uploadables: {
"input.file": file,
},
onCompleted: () => {
toast({
title: "Success",
description: "Compliance report uploaded successfully",
variant: "default",
});
loadQuery({ vendorId: data.node.id! });
},
onError: (error) => {
toast({
title: "Error",
description:
error.message || "Failed to upload compliance report",
variant: "destructive",
});
},
});
};
reader.readAsDataURL(file);
},
[uploadVendorComplianceReport, data.node.id, loadQuery, toast]
);
return (
<PageTemplate title={formData.name}>
<div className="max-w-2xl space-y-6">
@@ -370,6 +653,17 @@ function VendorViewContent({
</div>
</div>
</Card>
<Card className="p-6">
<ComplianceReportsTable
reports={
data.node.complianceReports?.edges.map((edge) => edge.node) ?? []
}
onDelete={handleDeleteReport}
onUpload={handleUploadReport}
/>
</Card>
<div className="mt-6 flex justify-end gap-2">
<Button variant="outline" onClick={handleCancel}>
Cancel

View File

@@ -0,0 +1,132 @@
/**
* @generated SignedSource<<a95a2943aea5d692a8f116bef3f91a6f>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DeleteVendorComplianceReportInput = {
reportId: string;
};
export type VendorViewDeleteComplianceReportMutation$variables = {
connections: ReadonlyArray<string>;
input: DeleteVendorComplianceReportInput;
};
export type VendorViewDeleteComplianceReportMutation$data = {
readonly deleteVendorComplianceReport: {
readonly deletedVendorComplianceReportId: string;
};
};
export type VendorViewDeleteComplianceReportMutation = {
response: VendorViewDeleteComplianceReportMutation$data;
variables: VendorViewDeleteComplianceReportMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "connections"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
},
v2 = [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deletedVendorComplianceReportId",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "VendorViewDeleteComplianceReportMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "DeleteVendorComplianceReportPayload",
"kind": "LinkedField",
"name": "deleteVendorComplianceReport",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "VendorViewDeleteComplianceReportMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "DeleteVendorComplianceReportPayload",
"kind": "LinkedField",
"name": "deleteVendorComplianceReport",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"filters": null,
"handle": "deleteEdge",
"key": "",
"kind": "ScalarHandle",
"name": "deletedVendorComplianceReportId",
"handleArgs": [
{
"kind": "Variable",
"name": "connections",
"variableName": "connections"
}
]
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "1e41cb2debc83547d78f3dd77236230a",
"id": null,
"metadata": {},
"name": "VendorViewDeleteComplianceReportMutation",
"operationKind": "mutation",
"text": "mutation VendorViewDeleteComplianceReportMutation(\n $input: DeleteVendorComplianceReportInput!\n) {\n deleteVendorComplianceReport(input: $input) {\n deletedVendorComplianceReportId\n }\n}\n"
}
};
})();
(node as any).hash = "78d3becac99c6b6d1b65ebb116b0a172";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<61507551e23b8cc70b401cab8ae4e575>>
* @generated SignedSource<<e84da0f625c9ae5495a0cb0c99c88c81>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -16,6 +16,19 @@ export type VendorViewQuery$variables = {
};
export type VendorViewQuery$data = {
readonly node: {
readonly complianceReports?: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly createdAt: string;
readonly fileSize: number;
readonly fileUrl: string;
readonly id: string;
readonly reportDate: string;
readonly reportName: string;
readonly validUntil: string | null | undefined;
};
}>;
};
readonly createdAt?: string;
readonly description?: string;
readonly id?: string;
@@ -133,7 +146,115 @@ v13 = {
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
};
},
v14 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v15 = [
{
"alias": null,
"args": null,
"concreteType": "VendorComplianceReportEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "VendorComplianceReport",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "reportName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "reportDate",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "validUntil",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fileUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fileSize",
"storageKey": null
},
(v12/*: any*/),
(v14/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
}
],
v16 = [
{
"kind": "Literal",
"name": "first",
"value": 100
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
@@ -163,7 +284,17 @@ return {
(v10/*: any*/),
(v11/*: any*/),
(v12/*: any*/),
(v13/*: any*/)
(v13/*: any*/),
{
"alias": "complianceReports",
"args": null,
"concreteType": "VendorComplianceReportConnection",
"kind": "LinkedField",
"name": "__VendorView_complianceReports_connection",
"plural": false,
"selections": (v15/*: any*/),
"storageKey": null
}
],
"type": "Vendor",
"abstractKey": null
@@ -189,13 +320,7 @@ return {
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
(v14/*: any*/),
(v2/*: any*/),
{
"kind": "InlineFragment",
@@ -210,7 +335,26 @@ return {
(v10/*: any*/),
(v11/*: any*/),
(v12/*: any*/),
(v13/*: any*/)
(v13/*: any*/),
{
"alias": null,
"args": (v16/*: any*/),
"concreteType": "VendorComplianceReportConnection",
"kind": "LinkedField",
"name": "complianceReports",
"plural": false,
"selections": (v15/*: any*/),
"storageKey": "complianceReports(first:100)"
},
{
"alias": null,
"args": (v16/*: any*/),
"filters": null,
"handle": "connection",
"key": "VendorView_complianceReports",
"kind": "LinkedHandle",
"name": "complianceReports"
}
],
"type": "Vendor",
"abstractKey": null
@@ -221,16 +365,28 @@ return {
]
},
"params": {
"cacheID": "40428ff15eb094ffe4cb5ffb5d135cc1",
"cacheID": "9d2e53dfbc545614d651819697eda6e6",
"id": null,
"metadata": {},
"metadata": {
"connection": [
{
"count": null,
"cursor": null,
"direction": "forward",
"path": [
"node",
"complianceReports"
]
}
]
},
"name": "VendorViewQuery",
"operationKind": "query",
"text": "query VendorViewQuery(\n $vendorId: ID!\n) {\n node(id: $vendorId) {\n __typename\n ... on Vendor {\n id\n name\n description\n serviceStartAt\n serviceTerminationAt\n serviceCriticality\n riskTier\n statusPageUrl\n termsOfServiceUrl\n privacyPolicyUrl\n createdAt\n updatedAt\n }\n id\n }\n}\n"
"text": "query VendorViewQuery(\n $vendorId: ID!\n) {\n node(id: $vendorId) {\n __typename\n ... on Vendor {\n id\n name\n description\n serviceStartAt\n serviceTerminationAt\n serviceCriticality\n riskTier\n statusPageUrl\n termsOfServiceUrl\n privacyPolicyUrl\n createdAt\n updatedAt\n complianceReports(first: 100) {\n edges {\n node {\n id\n reportName\n reportDate\n validUntil\n fileUrl\n fileSize\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n"
}
};
})();
(node as any).hash = "dbef9acdc02dd7e8cd546c0bb8793b9a";
(node as any).hash = "110583f1567e472ba6b4a6064be056e9";
export default node;

View File

@@ -0,0 +1,210 @@
/**
* @generated SignedSource<<2bcb17bf1104688db87ac4927bc4c0c7>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type UploadVendorComplianceReportInput = {
file: any;
reportDate: string;
reportName: string;
validUntil?: string | null | undefined;
vendorId: string;
};
export type VendorViewUploadComplianceReportMutation$variables = {
connections: ReadonlyArray<string>;
input: UploadVendorComplianceReportInput;
};
export type VendorViewUploadComplianceReportMutation$data = {
readonly uploadVendorComplianceReport: {
readonly vendorComplianceReportEdge: {
readonly node: {
readonly createdAt: string;
readonly fileSize: number;
readonly fileUrl: string;
readonly id: string;
readonly reportDate: string;
readonly reportName: string;
readonly validUntil: string | null | undefined;
};
};
};
};
export type VendorViewUploadComplianceReportMutation = {
response: VendorViewUploadComplianceReportMutation$data;
variables: VendorViewUploadComplianceReportMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "connections"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
},
v2 = [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
v3 = {
"alias": null,
"args": null,
"concreteType": "VendorComplianceReportEdge",
"kind": "LinkedField",
"name": "vendorComplianceReportEdge",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "VendorComplianceReport",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "reportName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "reportDate",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "validUntil",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fileUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fileSize",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "VendorViewUploadComplianceReportMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "UploadVendorComplianceReportPayload",
"kind": "LinkedField",
"name": "uploadVendorComplianceReport",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "VendorViewUploadComplianceReportMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "UploadVendorComplianceReportPayload",
"kind": "LinkedField",
"name": "uploadVendorComplianceReport",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"filters": null,
"handle": "appendEdge",
"key": "",
"kind": "LinkedHandle",
"name": "vendorComplianceReportEdge",
"handleArgs": [
{
"kind": "Variable",
"name": "connections",
"variableName": "connections"
}
]
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "6700bbabd4e074cc11d5dc7fa5485ea9",
"id": null,
"metadata": {},
"name": "VendorViewUploadComplianceReportMutation",
"operationKind": "mutation",
"text": "mutation VendorViewUploadComplianceReportMutation(\n $input: UploadVendorComplianceReportInput!\n) {\n uploadVendorComplianceReport(input: $input) {\n vendorComplianceReportEdge {\n node {\n id\n reportName\n reportDate\n validUntil\n fileUrl\n fileSize\n createdAt\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "1952e347045e900536855a11b826b106";
export default node;

View File

@@ -63,6 +63,7 @@ func (vcs *VendorComplianceReports) LoadForVendorID(
q := `
SELECT
id,
vendor_id,
report_date,
valid_until,
report_name,
@@ -199,7 +200,8 @@ func (vcr *VendorComplianceReport) Delete(
scope Scoper,
) error {
q := `
DELETE FROM
DELETE
FROM
vendor_compliance_reports
WHERE
%s
@@ -208,7 +210,8 @@ WHERE
q = fmt.Sprintf(q, scope.SQLFragment())
args := pgx.NamedArgs{"id": vcr.ID}
args := pgx.StrictNamedArgs{"id": vcr.ID}
maps.Copy(args, scope.SQLArguments())
_, err := conn.Exec(ctx, q, args)
return err

View File

@@ -186,10 +186,16 @@ func (s VendorComplianceReportService) Delete(
) error {
vendorComplianceReport := &coredata.VendorComplianceReport{ID: vendorComplianceReportID}
return s.svc.pg.WithConn(
err := s.svc.pg.WithConn(
ctx,
func(conn pg.Conn) error {
return vendorComplianceReport.Delete(ctx, conn, s.svc.scope)
},
)
if err != nil {
return fmt.Errorf("cannot delete vendor compliance report: %w", err)
}
return nil
}