@@ -17,30 +17,41 @@ import {
|
|||||||
useRelayEnvironment,
|
useRelayEnvironment,
|
||||||
} from "react-relay";
|
} from "react-relay";
|
||||||
import {
|
import {
|
||||||
|
AlertTriangle,
|
||||||
|
ArrowRight,
|
||||||
|
Check,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
Plus,
|
|
||||||
Trash2,
|
|
||||||
FileIcon,
|
|
||||||
Loader2,
|
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronUp,
|
ChevronUp,
|
||||||
|
Clock,
|
||||||
Download,
|
Download,
|
||||||
Eye,
|
Eye,
|
||||||
File as FileGeneric,
|
File as FileGeneric,
|
||||||
|
FileIcon,
|
||||||
FileText,
|
FileText,
|
||||||
Image,
|
Image,
|
||||||
X,
|
|
||||||
UserPlus,
|
|
||||||
UserMinus,
|
|
||||||
User,
|
|
||||||
Link2,
|
Link2,
|
||||||
|
Loader2,
|
||||||
|
LucideIcon,
|
||||||
|
MoreHorizontal,
|
||||||
|
Plus,
|
||||||
Search,
|
Search,
|
||||||
Link as LinkIcon,
|
|
||||||
CheckSquare,
|
|
||||||
ShieldCheck,
|
ShieldCheck,
|
||||||
AlertTriangle,
|
Trash2,
|
||||||
|
Upload,
|
||||||
|
User,
|
||||||
|
UserMinus,
|
||||||
|
UserPlus,
|
||||||
|
X,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@@ -83,7 +94,6 @@ import { MitigationViewSkeleton } from "./MitigationPage";
|
|||||||
import { MitigationViewUpdateTaskStateMutation as MitigationViewUpdateTaskStateMutationType } from "./__generated__/MitigationViewUpdateTaskStateMutation.graphql";
|
import { MitigationViewUpdateTaskStateMutation as MitigationViewUpdateTaskStateMutationType } from "./__generated__/MitigationViewUpdateTaskStateMutation.graphql";
|
||||||
import { MitigationViewCreateTaskMutation as MitigationViewCreateTaskMutationType } from "./__generated__/MitigationViewCreateTaskMutation.graphql";
|
import { MitigationViewCreateTaskMutation as MitigationViewCreateTaskMutationType } from "./__generated__/MitigationViewCreateTaskMutation.graphql";
|
||||||
import { MitigationViewDeleteTaskMutation as MitigationViewDeleteTaskMutationType } from "./__generated__/MitigationViewDeleteTaskMutation.graphql";
|
import { MitigationViewDeleteTaskMutation as MitigationViewDeleteTaskMutationType } from "./__generated__/MitigationViewDeleteTaskMutation.graphql";
|
||||||
import { MitigationViewUploadEvidenceMutation as MitigationViewUploadEvidenceMutationType } from "./__generated__/MitigationViewUploadEvidenceMutation.graphql";
|
|
||||||
import { MitigationViewDeleteEvidenceMutation as MitigationViewDeleteEvidenceMutationType } from "./__generated__/MitigationViewDeleteEvidenceMutation.graphql";
|
import { MitigationViewDeleteEvidenceMutation as MitigationViewDeleteEvidenceMutationType } from "./__generated__/MitigationViewDeleteEvidenceMutation.graphql";
|
||||||
import { MitigationViewAssignTaskMutation as MitigationViewAssignTaskMutationType } from "./__generated__/MitigationViewAssignTaskMutation.graphql";
|
import { MitigationViewAssignTaskMutation as MitigationViewAssignTaskMutationType } from "./__generated__/MitigationViewAssignTaskMutation.graphql";
|
||||||
import { MitigationViewUnassignTaskMutation as MitigationViewUnassignTaskMutationType } from "./__generated__/MitigationViewUnassignTaskMutation.graphql";
|
import { MitigationViewUnassignTaskMutation as MitigationViewUnassignTaskMutationType } from "./__generated__/MitigationViewUnassignTaskMutation.graphql";
|
||||||
@@ -105,6 +115,10 @@ import {
|
|||||||
MitigationViewRisksQuery,
|
MitigationViewRisksQuery,
|
||||||
MitigationViewRisksQuery$data,
|
MitigationViewRisksQuery$data,
|
||||||
} from "./__generated__/MitigationViewRisksQuery.graphql";
|
} from "./__generated__/MitigationViewRisksQuery.graphql";
|
||||||
|
import { MitigationViewCreateEvidenceMutation as MitigationViewCreateEvidenceMutationType } from "./__generated__/MitigationViewCreateEvidenceMutation.graphql";
|
||||||
|
import { MitigationViewGetEvidenceFileUrlQuery as MitigationViewGetEvidenceFileUrlQueryType } from "./__generated__/MitigationViewGetEvidenceFileUrlQuery.graphql";
|
||||||
|
import { MitigationViewFulfillEvidenceMutation as MitigationViewFulfillEvidenceMutationType } from "./__generated__/MitigationViewFulfillEvidenceMutation.graphql";
|
||||||
|
import { MitigationViewUploadEvidenceMutation as MitigationViewUploadEvidenceMutationType } from "./__generated__/MitigationViewUploadEvidenceMutation.graphql";
|
||||||
|
|
||||||
// Function to format ISO8601 duration to human-readable format
|
// Function to format ISO8601 duration to human-readable format
|
||||||
const formatDuration = (isoDuration: string): string => {
|
const formatDuration = (isoDuration: string): string => {
|
||||||
@@ -238,12 +252,12 @@ const deleteTaskMutation = graphql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const uploadEvidenceMutation = graphql`
|
const createEvidenceMutation = graphql`
|
||||||
mutation MitigationViewUploadEvidenceMutation(
|
mutation MitigationViewCreateEvidenceMutation(
|
||||||
$input: UploadEvidenceInput!
|
$input: CreateEvidenceInput!
|
||||||
$connections: [ID!]!
|
$connections: [ID!]!
|
||||||
) {
|
) {
|
||||||
uploadEvidence(input: $input) {
|
createEvidence(input: $input) {
|
||||||
evidenceEdge @appendEdge(connections: $connections) {
|
evidenceEdge @appendEdge(connections: $connections) {
|
||||||
node {
|
node {
|
||||||
id
|
id
|
||||||
@@ -416,6 +430,30 @@ const deleteControlMappingMutation = graphql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
// Add a mutation to fulfill evidence
|
||||||
|
const fulfillEvidenceMutation = graphql`
|
||||||
|
mutation MitigationViewFulfillEvidenceMutation(
|
||||||
|
$input: FulfillEvidenceInput!
|
||||||
|
$connections: [ID!]!
|
||||||
|
) {
|
||||||
|
fulfillEvidence(input: $input) {
|
||||||
|
evidenceEdge @appendEdge(connections: $connections) {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
filename
|
||||||
|
fileUrl
|
||||||
|
mimeType
|
||||||
|
type
|
||||||
|
url
|
||||||
|
size
|
||||||
|
state
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
// Add query to fetch risks linked to a mitigation
|
// Add query to fetch risks linked to a mitigation
|
||||||
const mitigationRisksQuery = graphql`
|
const mitigationRisksQuery = graphql`
|
||||||
query MitigationViewRisksQuery($mitigationId: ID!) {
|
query MitigationViewRisksQuery($mitigationId: ID!) {
|
||||||
@@ -641,9 +679,9 @@ function MitigationViewContent({
|
|||||||
useMutation<MitigationViewCreateTaskMutationType>(createTaskMutation);
|
useMutation<MitigationViewCreateTaskMutationType>(createTaskMutation);
|
||||||
const [deleteTask] =
|
const [deleteTask] =
|
||||||
useMutation<MitigationViewDeleteTaskMutationType>(deleteTaskMutation);
|
useMutation<MitigationViewDeleteTaskMutationType>(deleteTaskMutation);
|
||||||
const [uploadEvidence] =
|
const [createEvidence] =
|
||||||
useMutation<MitigationViewUploadEvidenceMutationType>(
|
useMutation<MitigationViewCreateEvidenceMutationType>(
|
||||||
uploadEvidenceMutation
|
createEvidenceMutation
|
||||||
);
|
);
|
||||||
const [deleteEvidence] =
|
const [deleteEvidence] =
|
||||||
useMutation<MitigationViewDeleteEvidenceMutationType>(
|
useMutation<MitigationViewDeleteEvidenceMutationType>(
|
||||||
@@ -653,6 +691,10 @@ function MitigationViewContent({
|
|||||||
useMutation<MitigationViewAssignTaskMutationType>(assignTaskMutation);
|
useMutation<MitigationViewAssignTaskMutationType>(assignTaskMutation);
|
||||||
const [unassignTask] =
|
const [unassignTask] =
|
||||||
useMutation<MitigationViewUnassignTaskMutationType>(unassignTaskMutation);
|
useMutation<MitigationViewUnassignTaskMutationType>(unassignTaskMutation);
|
||||||
|
const [fulfillEvidence] =
|
||||||
|
useMutation<MitigationViewFulfillEvidenceMutationType>(
|
||||||
|
fulfillEvidenceMutation
|
||||||
|
);
|
||||||
|
|
||||||
const [updateMitigationState] =
|
const [updateMitigationState] =
|
||||||
useMutation<MitigationViewUpdateMitigationStateMutationType>(
|
useMutation<MitigationViewUpdateMitigationStateMutationType>(
|
||||||
@@ -723,6 +765,14 @@ function MitigationViewContent({
|
|||||||
const [linkEvidenceDescription, setLinkEvidenceDescription] = useState("");
|
const [linkEvidenceDescription, setLinkEvidenceDescription] = useState("");
|
||||||
const [activeTab, setActiveTab] = useState<"file" | "link">("file");
|
const [activeTab, setActiveTab] = useState<"file" | "link">("file");
|
||||||
|
|
||||||
|
// State for fulfill evidence dialog
|
||||||
|
const [fulfillEvidenceDialogOpen, setFulfillEvidenceDialogOpen] =
|
||||||
|
useState(false);
|
||||||
|
const [evidenceToFulfill, setEvidenceToFulfill] = useState<{
|
||||||
|
id: string;
|
||||||
|
filename: string;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
// Add state for selected task panel
|
// Add state for selected task panel
|
||||||
const [selectedTask, setSelectedTask] = useState<(typeof tasks)[0] | null>(
|
const [selectedTask, setSelectedTask] = useState<(typeof tasks)[0] | null>(
|
||||||
null
|
null
|
||||||
@@ -955,7 +1005,7 @@ function MitigationViewContent({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUploadEvidence = (taskId: string, taskName: string) => {
|
const handleCreateEvidence = (taskId: string, taskName: string) => {
|
||||||
setTaskForEvidence({ id: taskId, name: taskName });
|
setTaskForEvidence({ id: taskId, name: taskName });
|
||||||
setEvidenceDialogOpen(true);
|
setEvidenceDialogOpen(true);
|
||||||
// Reset form fields
|
// Reset form fields
|
||||||
@@ -980,7 +1030,7 @@ function MitigationViewContent({
|
|||||||
// Get the evidence connection ID for this task
|
// Get the evidence connection ID for this task
|
||||||
const evidenceConnectionId = getEvidenceConnectionId(taskForEvidence.id);
|
const evidenceConnectionId = getEvidenceConnectionId(taskForEvidence.id);
|
||||||
|
|
||||||
uploadEvidence({
|
createEvidence({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
taskId: taskForEvidence.id,
|
taskId: taskForEvidence.id,
|
||||||
@@ -1043,7 +1093,7 @@ function MitigationViewContent({
|
|||||||
const description =
|
const description =
|
||||||
linkEvidenceDescription.trim() || `Link to ${linkEvidenceUrl}`;
|
linkEvidenceDescription.trim() || `Link to ${linkEvidenceUrl}`;
|
||||||
|
|
||||||
uploadEvidence({
|
createEvidence({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
taskId: taskForEvidence.id,
|
taskId: taskForEvidence.id,
|
||||||
@@ -1109,7 +1159,7 @@ function MitigationViewContent({
|
|||||||
// Get the evidence connection ID for this task
|
// Get the evidence connection ID for this task
|
||||||
const evidenceConnectionId = getEvidenceConnectionId(taskId);
|
const evidenceConnectionId = getEvidenceConnectionId(taskId);
|
||||||
|
|
||||||
uploadEvidence({
|
createEvidence({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
taskId: taskId,
|
taskId: taskId,
|
||||||
@@ -1236,6 +1286,126 @@ function MitigationViewContent({
|
|||||||
setIsDeleteEvidenceOpen(true);
|
setIsDeleteEvidenceOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleFulfillEvidence = (evidenceId: string, filename: string) => {
|
||||||
|
setEvidenceToFulfill({ id: evidenceId, filename });
|
||||||
|
setFulfillEvidenceDialogOpen(true);
|
||||||
|
setActiveTab("file");
|
||||||
|
setLinkEvidenceUrl("");
|
||||||
|
setLinkEvidenceDescription("");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFulfillEvidenceWithFile = (
|
||||||
|
e: React.ChangeEvent<HTMLInputElement>
|
||||||
|
) => {
|
||||||
|
if (!e.target.files || e.target.files.length === 0 || !evidenceToFulfill)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const file = e.target.files[0];
|
||||||
|
|
||||||
|
// Show toast for upload started
|
||||||
|
toast({
|
||||||
|
title: "Uploading evidence",
|
||||||
|
description: `Uploading ${file.name}...`,
|
||||||
|
variant: "default",
|
||||||
|
});
|
||||||
|
|
||||||
|
const evidenceId = evidenceToFulfill.id;
|
||||||
|
// Get connection ID for the parent task (assuming it's available in the view)
|
||||||
|
const task = tasks.find((task) =>
|
||||||
|
task.evidences?.edges.some((edge) => edge?.node?.id === evidenceId)
|
||||||
|
);
|
||||||
|
|
||||||
|
const evidenceConnectionId = task?.id
|
||||||
|
? getEvidenceConnectionId(task.id)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
fulfillEvidence({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
evidenceId: evidenceId,
|
||||||
|
file: null,
|
||||||
|
},
|
||||||
|
connections: evidenceConnectionId ? [evidenceConnectionId] : [],
|
||||||
|
},
|
||||||
|
uploadables: {
|
||||||
|
"input.file": file,
|
||||||
|
},
|
||||||
|
onCompleted: () => {
|
||||||
|
setFulfillEvidenceDialogOpen(false);
|
||||||
|
setEvidenceToFulfill(null);
|
||||||
|
toast({
|
||||||
|
title: "Evidence uploaded",
|
||||||
|
description: "Evidence has been fulfilled successfully.",
|
||||||
|
variant: "default",
|
||||||
|
});
|
||||||
|
// Reset the file input
|
||||||
|
if (hiddenFileInputRef.current) {
|
||||||
|
hiddenFileInputRef.current.value = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast({
|
||||||
|
title: "Error uploading evidence",
|
||||||
|
description: error.message,
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFulfillEvidenceWithLink = () => {
|
||||||
|
if (!evidenceToFulfill) return;
|
||||||
|
|
||||||
|
// Validate URL
|
||||||
|
if (!linkEvidenceUrl.trim()) {
|
||||||
|
toast({
|
||||||
|
title: "Error",
|
||||||
|
description: "Please provide a URL for the evidence",
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get connection ID for the parent task
|
||||||
|
const task = tasks.find((task) =>
|
||||||
|
task.evidences?.edges.some(
|
||||||
|
(edge) => edge?.node?.id === evidenceToFulfill.id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const evidenceConnectionId = task?.id
|
||||||
|
? getEvidenceConnectionId(task.id)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
fulfillEvidence({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
evidenceId: evidenceToFulfill.id,
|
||||||
|
url: linkEvidenceUrl,
|
||||||
|
},
|
||||||
|
connections: evidenceConnectionId ? [evidenceConnectionId] : [],
|
||||||
|
},
|
||||||
|
onCompleted: () => {
|
||||||
|
setFulfillEvidenceDialogOpen(false);
|
||||||
|
setEvidenceToFulfill(null);
|
||||||
|
setLinkEvidenceUrl("");
|
||||||
|
setLinkEvidenceDescription("");
|
||||||
|
toast({
|
||||||
|
title: "Evidence updated",
|
||||||
|
description: "Evidence has been fulfilled with link successfully.",
|
||||||
|
variant: "default",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
toast({
|
||||||
|
title: "Error updating evidence",
|
||||||
|
description: error.message,
|
||||||
|
variant: "destructive",
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const confirmDeleteEvidence = () => {
|
const confirmDeleteEvidence = () => {
|
||||||
if (!evidenceToDelete) return;
|
if (!evidenceToDelete) return;
|
||||||
|
|
||||||
@@ -1789,7 +1959,7 @@ function MitigationViewContent({
|
|||||||
>
|
>
|
||||||
<TabsList className="mb-4">
|
<TabsList className="mb-4">
|
||||||
<TabsTrigger value="tasks" className="flex items-center gap-2">
|
<TabsTrigger value="tasks" className="flex items-center gap-2">
|
||||||
<CheckSquare className="w-4 h-4" />
|
<Check className="w-4 h-4" />
|
||||||
Tasks
|
Tasks
|
||||||
{tasks.length > 0 && (
|
{tasks.length > 0 && (
|
||||||
<span className="ml-1.5 bg-blue-100 text-blue-800 rounded-full text-xs px-2 py-0.5">
|
<span className="ml-1.5 bg-blue-100 text-blue-800 rounded-full text-xs px-2 py-0.5">
|
||||||
@@ -1830,7 +2000,7 @@ function MitigationViewContent({
|
|||||||
className="flex items-center gap-1"
|
className="flex items-center gap-1"
|
||||||
onClick={handleOpenControlMappingDialog}
|
onClick={handleOpenControlMappingDialog}
|
||||||
>
|
>
|
||||||
<LinkIcon className="w-4 h-4" />
|
<Link2 className="w-4 h-4" />
|
||||||
<span>Map to Controls</span>
|
<span>Map to Controls</span>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1967,7 +2137,7 @@ function MitigationViewContent({
|
|||||||
{isLinkingControl ? (
|
{isLinkingControl ? (
|
||||||
<Loader2 className="w-4 h-4 animate-spin" />
|
<Loader2 className="w-4 h-4 animate-spin" />
|
||||||
) : (
|
) : (
|
||||||
<LinkIcon className="w-4 h-4" />
|
<Link2 className="w-4 h-4" />
|
||||||
)}
|
)}
|
||||||
<span className="ml-1">Link</span>
|
<span className="ml-1">Link</span>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -2442,7 +2612,7 @@ function MitigationViewContent({
|
|||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (task?.id && task?.name) {
|
if (task?.id && task?.name) {
|
||||||
handleUploadEvidence(task.id, task.name);
|
handleCreateEvidence(task.id, task.name);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
title="Add Evidence"
|
title="Add Evidence"
|
||||||
@@ -2512,16 +2682,31 @@ function MitigationViewContent({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-medium text-gray-800">
|
<div className="text-sm font-medium text-gray-800 flex items-center gap-2">
|
||||||
{evidence.filename}
|
{evidence.filename}
|
||||||
|
{evidence.state === "REQUESTED" && (
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="text-xs bg-yellow-50 text-yellow-700 border-yellow-200"
|
||||||
|
>
|
||||||
|
Requested
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500 flex items-center gap-2 mt-0.5">
|
<div className="text-xs text-gray-500 flex items-center gap-2 mt-0.5">
|
||||||
{evidence.type === "FILE" ? (
|
{evidence.state === "REQUESTED" ? (
|
||||||
|
<span className="italic text-yellow-600">
|
||||||
|
Waiting for fulfillment
|
||||||
|
</span>
|
||||||
|
) : evidence.type === "FILE" ? (
|
||||||
<>
|
<>
|
||||||
<span className="font-medium text-gray-600">
|
<span className="font-medium text-gray-600">
|
||||||
{formatFileSize(evidence.size)}
|
{formatFileSize(evidence.size)}
|
||||||
</span>
|
</span>
|
||||||
<span>•</span>
|
<span>•</span>
|
||||||
|
<span>
|
||||||
|
{formatDate(evidence.createdAt)}
|
||||||
|
</span>
|
||||||
</>
|
</>
|
||||||
) : evidence.url ? (
|
) : evidence.url ? (
|
||||||
<>
|
<>
|
||||||
@@ -2529,16 +2714,34 @@ function MitigationViewContent({
|
|||||||
{evidence.url}
|
{evidence.url}
|
||||||
</span>
|
</span>
|
||||||
<span>•</span>
|
<span>•</span>
|
||||||
|
<span>
|
||||||
|
{formatDate(evidence.createdAt)}
|
||||||
|
</span>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : (
|
||||||
<span>
|
<span>
|
||||||
{formatDate(evidence.createdAt)}
|
{formatDate(evidence.createdAt)}
|
||||||
</span>
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{evidence.type === "FILE" ? (
|
{evidence.state === "REQUESTED" ? (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleFulfillEvidence(
|
||||||
|
evidence.id,
|
||||||
|
evidence.filename
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
className="p-1.5 rounded-full hover:bg-white hover:shadow-sm transition-all"
|
||||||
|
title="Fulfill Evidence"
|
||||||
|
>
|
||||||
|
<Upload className="w-4 h-4 text-green-600" />
|
||||||
|
</button>
|
||||||
|
) : evidence.type === "FILE" ? (
|
||||||
<>
|
<>
|
||||||
{evidence.mimeType.startsWith(
|
{evidence.mimeType.startsWith(
|
||||||
"image/"
|
"image/"
|
||||||
@@ -2583,13 +2786,11 @@ function MitigationViewContent({
|
|||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (task.id) {
|
handleDeleteEvidence(
|
||||||
handleDeleteEvidence(
|
evidence.id,
|
||||||
evidence.id,
|
evidence.filename,
|
||||||
evidence.filename,
|
task.id
|
||||||
task.id
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
className="p-1.5 rounded-full hover:bg-red-50 hover:shadow-sm transition-all"
|
className="p-1.5 rounded-full hover:bg-red-50 hover:shadow-sm transition-all"
|
||||||
title="Delete"
|
title="Delete"
|
||||||
@@ -2947,7 +3148,7 @@ function MitigationViewContent({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleUploadEvidence(selectedTask.id, selectedTask.name)
|
handleCreateEvidence(selectedTask.id, selectedTask.name)
|
||||||
}
|
}
|
||||||
className="flex items-center gap-1 text-xs"
|
className="flex items-center gap-1 text-xs"
|
||||||
>
|
>
|
||||||
@@ -2974,16 +3175,31 @@ function MitigationViewContent({
|
|||||||
{getFileIcon(evidence.mimeType, evidence.type)}
|
{getFileIcon(evidence.mimeType, evidence.type)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm font-medium text-gray-800">
|
<div className="text-sm font-medium text-gray-800 flex items-center gap-2">
|
||||||
{evidence.filename}
|
{evidence.filename}
|
||||||
|
{evidence.state === "REQUESTED" && (
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="text-xs bg-yellow-50 text-yellow-700 border-yellow-200"
|
||||||
|
>
|
||||||
|
Requested
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500 flex items-center gap-2 mt-0.5">
|
<div className="text-xs text-gray-500 flex items-center gap-2 mt-0.5">
|
||||||
{evidence.type === "FILE" ? (
|
{evidence.state === "REQUESTED" ? (
|
||||||
|
<span className="italic text-yellow-600">
|
||||||
|
Waiting for fulfillment
|
||||||
|
</span>
|
||||||
|
) : evidence.type === "FILE" ? (
|
||||||
<>
|
<>
|
||||||
<span className="font-medium text-gray-600">
|
<span className="font-medium text-gray-600">
|
||||||
{formatFileSize(evidence.size)}
|
{formatFileSize(evidence.size)}
|
||||||
</span>
|
</span>
|
||||||
<span>•</span>
|
<span>•</span>
|
||||||
|
<span>
|
||||||
|
{formatDate(evidence.createdAt)}
|
||||||
|
</span>
|
||||||
</>
|
</>
|
||||||
) : evidence.url ? (
|
) : evidence.url ? (
|
||||||
<>
|
<>
|
||||||
@@ -2991,20 +3207,41 @@ function MitigationViewContent({
|
|||||||
{evidence.url}
|
{evidence.url}
|
||||||
</span>
|
</span>
|
||||||
<span>•</span>
|
<span>•</span>
|
||||||
|
<span>
|
||||||
|
{formatDate(evidence.createdAt)}
|
||||||
|
</span>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : (
|
||||||
<span>{formatDate(evidence.createdAt)}</span>
|
<span>
|
||||||
|
{formatDate(evidence.createdAt)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{evidence.type === "FILE" ? (
|
{evidence.state === "REQUESTED" ? (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleFulfillEvidence(
|
||||||
|
evidence.id,
|
||||||
|
evidence.filename
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
className="p-1.5 rounded-full hover:bg-white hover:shadow-sm transition-all"
|
||||||
|
title="Fulfill Evidence"
|
||||||
|
>
|
||||||
|
<Upload className="w-4 h-4 text-green-600" />
|
||||||
|
</button>
|
||||||
|
) : evidence.type === "FILE" ? (
|
||||||
<>
|
<>
|
||||||
{evidence.mimeType.startsWith("image/") ? (
|
{evidence.mimeType.startsWith("image/") ? (
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={(e) => {
|
||||||
handlePreviewEvidence(evidence)
|
e.stopPropagation();
|
||||||
}
|
handlePreviewEvidence(evidence);
|
||||||
|
}}
|
||||||
className="p-1.5 rounded-full hover:bg-white hover:shadow-sm transition-all"
|
className="p-1.5 rounded-full hover:bg-white hover:shadow-sm transition-all"
|
||||||
title="Preview Image"
|
title="Preview Image"
|
||||||
>
|
>
|
||||||
@@ -3013,7 +3250,7 @@ function MitigationViewContent({
|
|||||||
) : (
|
) : (
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.stopPropagation();
|
||||||
handlePreviewEvidence(evidence);
|
handlePreviewEvidence(evidence);
|
||||||
}}
|
}}
|
||||||
className="p-1.5 rounded-full hover:bg-white hover:shadow-sm transition-all"
|
className="p-1.5 rounded-full hover:bg-white hover:shadow-sm transition-all"
|
||||||
@@ -3026,7 +3263,7 @@ function MitigationViewContent({
|
|||||||
) : evidence.url ? (
|
) : evidence.url ? (
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.stopPropagation();
|
||||||
if (evidence.url) {
|
if (evidence.url) {
|
||||||
window.open(evidence.url, "_blank");
|
window.open(evidence.url, "_blank");
|
||||||
}
|
}
|
||||||
@@ -3039,11 +3276,11 @@ function MitigationViewContent({
|
|||||||
) : null}
|
) : null}
|
||||||
<button
|
<button
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.stopPropagation();
|
||||||
handleDeleteEvidence(
|
handleDeleteEvidence(
|
||||||
evidence.id,
|
evidence.id,
|
||||||
evidence.filename,
|
evidence.filename,
|
||||||
selectedTask.id
|
task.id
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
className="p-1.5 rounded-full hover:bg-red-50 hover:shadow-sm transition-all"
|
className="p-1.5 rounded-full hover:bg-red-50 hover:shadow-sm transition-all"
|
||||||
@@ -3353,6 +3590,104 @@ function MitigationViewContent({
|
|||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Fulfill Evidence Dialog */}
|
||||||
|
<Dialog
|
||||||
|
open={fulfillEvidenceDialogOpen}
|
||||||
|
onOpenChange={setFulfillEvidenceDialogOpen}
|
||||||
|
>
|
||||||
|
<DialogContent className="sm:max-w-[525px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Fulfill Evidence</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Provide the requested evidence: {evidenceToFulfill?.filename}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<Tabs
|
||||||
|
value={activeTab}
|
||||||
|
onValueChange={(value: string) =>
|
||||||
|
setActiveTab(value as "file" | "link")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TabsList className="grid w-full grid-cols-2">
|
||||||
|
<TabsTrigger value="file">Document</TabsTrigger>
|
||||||
|
<TabsTrigger value="link">Link</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsContent value="file" className="space-y-4">
|
||||||
|
<div className="space-y-4 pt-4">
|
||||||
|
<p>Upload a document to fulfill this evidence request.</p>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
if (hiddenFileInputRef.current) {
|
||||||
|
// Set a temp onchange handler for the file input
|
||||||
|
const originalOnChange =
|
||||||
|
hiddenFileInputRef.current.onchange;
|
||||||
|
hiddenFileInputRef.current.onchange = (e) => {
|
||||||
|
hiddenFileInputRef.current!.onchange =
|
||||||
|
originalOnChange as any;
|
||||||
|
handleFulfillEvidenceWithFile({
|
||||||
|
target: {
|
||||||
|
files: (e.target as HTMLInputElement)?.files,
|
||||||
|
},
|
||||||
|
} as React.ChangeEvent<HTMLInputElement>);
|
||||||
|
};
|
||||||
|
hiddenFileInputRef.current.click();
|
||||||
|
setFulfillEvidenceDialogOpen(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
Select Document
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value="link" className="space-y-4 pt-4">
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="grid w-full items-center gap-1.5">
|
||||||
|
<Label htmlFor="evidence-url">URL</Label>
|
||||||
|
<Input
|
||||||
|
id="evidence-url"
|
||||||
|
value={linkEvidenceUrl}
|
||||||
|
onChange={(e) => setLinkEvidenceUrl(e.target.value)}
|
||||||
|
placeholder="https://example.com"
|
||||||
|
type="url"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid w-full items-center gap-1.5">
|
||||||
|
<Label htmlFor="evidence-description">
|
||||||
|
Description (optional)
|
||||||
|
</Label>
|
||||||
|
<Textarea
|
||||||
|
id="evidence-description"
|
||||||
|
value={linkEvidenceDescription}
|
||||||
|
onChange={(e) => setLinkEvidenceDescription(e.target.value)}
|
||||||
|
placeholder="Describe this evidence (optional)"
|
||||||
|
className="min-h-[100px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setFulfillEvidenceDialogOpen(false)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
{activeTab === "link" && (
|
||||||
|
<Button onClick={handleFulfillEvidenceWithLink}>
|
||||||
|
Submit Link
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</PageTemplate>
|
</PageTemplate>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,229 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<beb765793d4559382ee625dffd7a510c>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type EvidenceState = "FULFILLED" | "REQUESTED";
|
||||||
|
export type EvidenceType = "FILE" | "LINK";
|
||||||
|
export type CreateEvidenceInput = {
|
||||||
|
description: string;
|
||||||
|
file?: any | null | undefined;
|
||||||
|
name: string;
|
||||||
|
taskId: string;
|
||||||
|
type: EvidenceType;
|
||||||
|
url?: string | null | undefined;
|
||||||
|
};
|
||||||
|
export type MitigationViewCreateEvidenceMutation$variables = {
|
||||||
|
connections: ReadonlyArray<string>;
|
||||||
|
input: CreateEvidenceInput;
|
||||||
|
};
|
||||||
|
export type MitigationViewCreateEvidenceMutation$data = {
|
||||||
|
readonly createEvidence: {
|
||||||
|
readonly evidenceEdge: {
|
||||||
|
readonly node: {
|
||||||
|
readonly createdAt: string;
|
||||||
|
readonly fileUrl: string | null | undefined;
|
||||||
|
readonly filename: string;
|
||||||
|
readonly id: string;
|
||||||
|
readonly mimeType: string;
|
||||||
|
readonly size: number;
|
||||||
|
readonly state: EvidenceState;
|
||||||
|
readonly type: EvidenceType;
|
||||||
|
readonly url: string | null | undefined;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type MitigationViewCreateEvidenceMutation = {
|
||||||
|
response: MitigationViewCreateEvidenceMutation$data;
|
||||||
|
variables: MitigationViewCreateEvidenceMutation$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": "EvidenceEdge",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "evidenceEdge",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Evidence",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "filename",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "fileUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "mimeType",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "type",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "url",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "size",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "state",
|
||||||
|
"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": "MitigationViewCreateEvidenceMutation",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v2/*: any*/),
|
||||||
|
"concreteType": "CreateEvidencePayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "createEvidence",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Mutation",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": [
|
||||||
|
(v1/*: any*/),
|
||||||
|
(v0/*: any*/)
|
||||||
|
],
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "MitigationViewCreateEvidenceMutation",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v2/*: any*/),
|
||||||
|
"concreteType": "CreateEvidencePayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "createEvidence",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v3/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"filters": null,
|
||||||
|
"handle": "appendEdge",
|
||||||
|
"key": "",
|
||||||
|
"kind": "LinkedHandle",
|
||||||
|
"name": "evidenceEdge",
|
||||||
|
"handleArgs": [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "connections",
|
||||||
|
"variableName": "connections"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "ca3148cb5b3c59fec3bd9ff9c281f364",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "MitigationViewCreateEvidenceMutation",
|
||||||
|
"operationKind": "mutation",
|
||||||
|
"text": "mutation MitigationViewCreateEvidenceMutation(\n $input: CreateEvidenceInput!\n) {\n createEvidence(input: $input) {\n evidenceEdge {\n node {\n id\n filename\n fileUrl\n mimeType\n type\n url\n size\n state\n createdAt\n }\n }\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "dac05ab414b2e09e801df5b0be9532a3";
|
||||||
|
|
||||||
|
export default node;
|
||||||
@@ -0,0 +1,226 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<c12714c0dab6d31865a2b49936566f05>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type EvidenceState = "FULFILLED" | "REQUESTED";
|
||||||
|
export type EvidenceType = "FILE" | "LINK";
|
||||||
|
export type FulfillEvidenceInput = {
|
||||||
|
evidenceId: string;
|
||||||
|
file?: any | null | undefined;
|
||||||
|
url?: string | null | undefined;
|
||||||
|
};
|
||||||
|
export type MitigationViewFulfillEvidenceMutation$variables = {
|
||||||
|
connections: ReadonlyArray<string>;
|
||||||
|
input: FulfillEvidenceInput;
|
||||||
|
};
|
||||||
|
export type MitigationViewFulfillEvidenceMutation$data = {
|
||||||
|
readonly fulfillEvidence: {
|
||||||
|
readonly evidenceEdge: {
|
||||||
|
readonly node: {
|
||||||
|
readonly createdAt: string;
|
||||||
|
readonly fileUrl: string | null | undefined;
|
||||||
|
readonly filename: string;
|
||||||
|
readonly id: string;
|
||||||
|
readonly mimeType: string;
|
||||||
|
readonly size: number;
|
||||||
|
readonly state: EvidenceState;
|
||||||
|
readonly type: EvidenceType;
|
||||||
|
readonly url: string | null | undefined;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type MitigationViewFulfillEvidenceMutation = {
|
||||||
|
response: MitigationViewFulfillEvidenceMutation$data;
|
||||||
|
variables: MitigationViewFulfillEvidenceMutation$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": "EvidenceEdge",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "evidenceEdge",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Evidence",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "filename",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "fileUrl",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "mimeType",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "type",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "url",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "size",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "state",
|
||||||
|
"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": "MitigationViewFulfillEvidenceMutation",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v2/*: any*/),
|
||||||
|
"concreteType": "FulfillEvidencePayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "fulfillEvidence",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Mutation",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": [
|
||||||
|
(v1/*: any*/),
|
||||||
|
(v0/*: any*/)
|
||||||
|
],
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "MitigationViewFulfillEvidenceMutation",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v2/*: any*/),
|
||||||
|
"concreteType": "FulfillEvidencePayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "fulfillEvidence",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v3/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"filters": null,
|
||||||
|
"handle": "appendEdge",
|
||||||
|
"key": "",
|
||||||
|
"kind": "LinkedHandle",
|
||||||
|
"name": "evidenceEdge",
|
||||||
|
"handleArgs": [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "connections",
|
||||||
|
"variableName": "connections"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "147fdc881f797ace12dc1242cab782ad",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "MitigationViewFulfillEvidenceMutation",
|
||||||
|
"operationKind": "mutation",
|
||||||
|
"text": "mutation MitigationViewFulfillEvidenceMutation(\n $input: FulfillEvidenceInput!\n) {\n fulfillEvidence(input: $input) {\n evidenceEdge {\n node {\n id\n filename\n fileUrl\n mimeType\n type\n url\n size\n state\n createdAt\n }\n }\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "c0a1e6be43759cfbcd7e25a3b597926a";
|
||||||
|
|
||||||
|
export default node;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<5baf9229b2f520bcfa43960bd6af05e0>>
|
* @generated SignedSource<<3188657dfa050c818bf0dc06688d7fab>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
|
||||||
import { ConcreteRequest } from 'relay-runtime';
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
export type EvidenceState = "EXPIRED" | "INVALID" | "VALID";
|
export type EvidenceState = "FULFILLED" | "REQUESTED";
|
||||||
export type EvidenceType = "FILE" | "LINK";
|
export type EvidenceType = "FILE" | "LINK";
|
||||||
export type MitigationImportance = "ADVANCED" | "MANDATORY" | "PREFERRED";
|
export type MitigationImportance = "ADVANCED" | "MANDATORY" | "PREFERRED";
|
||||||
export type MitigationState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED";
|
export type MitigationState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<9d4db1a6dc5ce7a35be41b6f42bc91d2>>
|
* @generated SignedSource<<6e5efc598a8e6657fd0a1375e6104ff6>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
|
|
||||||
import { ConcreteRequest } from 'relay-runtime';
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
export type EvidenceState = "EXPIRED" | "INVALID" | "VALID";
|
export type EvidenceState = "FULLFILLED" | "REQUESTED";
|
||||||
export type EvidenceType = "FILE" | "LINK";
|
export type EvidenceType = "FILE" | "LINK";
|
||||||
export type UploadEvidenceInput = {
|
export type UploadEvidenceInput = {
|
||||||
description: string;
|
description: string;
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import (
|
|||||||
type (
|
type (
|
||||||
Evidence struct {
|
Evidence struct {
|
||||||
ID gid.GID `db:"id"`
|
ID gid.GID `db:"id"`
|
||||||
TaskID *gid.GID `db:"task_id"`
|
TaskID gid.GID `db:"task_id"`
|
||||||
State EvidenceState `db:"state"`
|
State EvidenceState `db:"state"`
|
||||||
Type EvidenceType `db:"type"`
|
Type EvidenceType `db:"type"`
|
||||||
ObjectKey string `db:"object_key"`
|
ObjectKey string `db:"object_key"`
|
||||||
|
|||||||
@@ -24,9 +24,8 @@ type (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
EvidenceStateValid EvidenceState = iota
|
EvidenceStateRequested EvidenceState = iota
|
||||||
EvidenceStateInvalid
|
EvidenceStateFulfilled
|
||||||
EvidenceStateExpired
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (es EvidenceState) MarshalText() ([]byte, error) {
|
func (es EvidenceState) MarshalText() ([]byte, error) {
|
||||||
@@ -37,12 +36,10 @@ func (es *EvidenceState) UnmarshalText(data []byte) error {
|
|||||||
val := string(data)
|
val := string(data)
|
||||||
|
|
||||||
switch val {
|
switch val {
|
||||||
case EvidenceStateValid.String():
|
case EvidenceStateRequested.String():
|
||||||
*es = EvidenceStateValid
|
*es = EvidenceStateRequested
|
||||||
case EvidenceStateInvalid.String():
|
case EvidenceStateFulfilled.String():
|
||||||
*es = EvidenceStateInvalid
|
*es = EvidenceStateFulfilled
|
||||||
case EvidenceStateExpired.String():
|
|
||||||
*es = EvidenceStateExpired
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("invalid EvidenceState value: %q", val)
|
return fmt.Errorf("invalid EvidenceState value: %q", val)
|
||||||
}
|
}
|
||||||
@@ -54,12 +51,10 @@ func (es EvidenceState) String() string {
|
|||||||
var val string
|
var val string
|
||||||
|
|
||||||
switch es {
|
switch es {
|
||||||
case EvidenceStateValid:
|
case EvidenceStateRequested:
|
||||||
val = "VALID"
|
val = "REQUESTED"
|
||||||
case EvidenceStateInvalid:
|
case EvidenceStateFulfilled:
|
||||||
val = "INVALID"
|
val = "FULFILLED"
|
||||||
case EvidenceStateExpired:
|
|
||||||
val = "EXPIRED"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return val
|
return val
|
||||||
|
|||||||
10
pkg/coredata/migrations/20250402T083700Z.sql
Normal file
10
pkg/coredata/migrations/20250402T083700Z.sql
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
CREATE TYPE evidence_state_new AS ENUM ('FULFILLED', 'REQUESTED');
|
||||||
|
|
||||||
|
ALTER TABLE evidences ALTER COLUMN state TYPE text;
|
||||||
|
|
||||||
|
UPDATE evidences SET state = 'FULFILLED';
|
||||||
|
|
||||||
|
ALTER TABLE evidences ALTER COLUMN state TYPE evidence_state_new USING state::evidence_state_new;
|
||||||
|
|
||||||
|
DROP TYPE evidence_state;
|
||||||
|
ALTER TYPE evidence_state_new RENAME TO evidence_state;
|
||||||
1
pkg/coredata/migrations/20250402T093900Z.sql
Normal file
1
pkg/coredata/migrations/20250402T093900Z.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE evidences ALTER COLUMN task_id SET NOT NULL;
|
||||||
@@ -61,7 +61,7 @@ LOOP:
|
|||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
case <-time.After(1 * time.Second):
|
case <-time.After(60 * time.Second):
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if err := m.batchSendEmails(ctx); err != nil {
|
if err := m.batchSendEmails(ctx); err != nil {
|
||||||
m.l.ErrorCtx(ctx, "cannot send email", log.Error(err))
|
m.l.ErrorCtx(ctx, "cannot send email", log.Error(err))
|
||||||
|
|||||||
@@ -37,6 +37,19 @@ type (
|
|||||||
svc *TenantService
|
svc *TenantService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RequestEvidenceRequest struct {
|
||||||
|
TaskID gid.GID
|
||||||
|
Type coredata.EvidenceType
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
FulfilledEvidenceRequest struct {
|
||||||
|
EvidenceID gid.GID
|
||||||
|
File io.Reader
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
CreateEvidenceRequest struct {
|
CreateEvidenceRequest struct {
|
||||||
TaskID gid.GID
|
TaskID gid.GID
|
||||||
Name string
|
Name string
|
||||||
@@ -67,6 +80,63 @@ func (s EvidenceService) Get(
|
|||||||
return evidence, nil
|
return evidence, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s EvidenceService) Request(
|
||||||
|
ctx context.Context,
|
||||||
|
req RequestEvidenceRequest,
|
||||||
|
) (*coredata.Evidence, error) {
|
||||||
|
evidenceID, err := gid.NewGID(s.svc.scope.GetTenantID(), coredata.EvidenceEntityType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot create evidence global id: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
evidence := &coredata.Evidence{
|
||||||
|
ID: evidenceID,
|
||||||
|
TaskID: req.TaskID,
|
||||||
|
State: coredata.EvidenceStateRequested,
|
||||||
|
Type: req.Type,
|
||||||
|
Filename: req.Name,
|
||||||
|
Description: req.Description,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.svc.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
return evidence.Insert(ctx, conn, s.svc.scope)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cannot insert evidence: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return evidence, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s EvidenceService) Fulfill(
|
||||||
|
ctx context.Context,
|
||||||
|
req FulfilledEvidenceRequest,
|
||||||
|
) (*coredata.Evidence, error) {
|
||||||
|
evidence := &coredata.Evidence{}
|
||||||
|
|
||||||
|
err := s.svc.pg.WithTx(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
if err := evidence.LoadByID(ctx, conn, s.svc.scope, req.EvidenceID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load evidence: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
evidence.State = coredata.EvidenceStateFulfilled
|
||||||
|
|
||||||
|
return evidence.Update(ctx, conn, s.svc.scope)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func (s EvidenceService) Create(
|
func (s EvidenceService) Create(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req CreateEvidenceRequest,
|
req CreateEvidenceRequest,
|
||||||
@@ -79,8 +149,8 @@ func (s EvidenceService) Create(
|
|||||||
|
|
||||||
evidence := &coredata.Evidence{
|
evidence := &coredata.Evidence{
|
||||||
ID: evidenceID,
|
ID: evidenceID,
|
||||||
TaskID: &req.TaskID,
|
TaskID: req.TaskID,
|
||||||
State: coredata.EvidenceStateValid,
|
State: coredata.EvidenceStateFulfilled,
|
||||||
Type: req.Type,
|
Type: req.Type,
|
||||||
Filename: req.Name,
|
Filename: req.Name,
|
||||||
URL: req.URL,
|
URL: req.URL,
|
||||||
|
|||||||
@@ -67,15 +67,13 @@ enum TaskState
|
|||||||
|
|
||||||
enum EvidenceState
|
enum EvidenceState
|
||||||
@goModel(model: "github.com/getprobo/probo/pkg/coredata.EvidenceState") {
|
@goModel(model: "github.com/getprobo/probo/pkg/coredata.EvidenceState") {
|
||||||
VALID
|
FULFILLED
|
||||||
@goEnum(value: "github.com/getprobo/probo/pkg/coredata.EvidenceStateValid")
|
|
||||||
INVALID
|
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.EvidenceStateInvalid"
|
value: "github.com/getprobo/probo/pkg/coredata.EvidenceStateFulfilled"
|
||||||
)
|
)
|
||||||
EXPIRED
|
REQUESTED
|
||||||
@goEnum(
|
@goEnum(
|
||||||
value: "github.com/getprobo/probo/pkg/coredata.EvidenceStateExpired"
|
value: "github.com/getprobo/probo/pkg/coredata.EvidenceStateRequested"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -751,7 +749,9 @@ type Mutation {
|
|||||||
deleteRiskMapping(input: DeleteRiskMappingInput!): DeleteRiskMappingPayload!
|
deleteRiskMapping(input: DeleteRiskMappingInput!): DeleteRiskMappingPayload!
|
||||||
|
|
||||||
# Evidence mutations
|
# Evidence mutations
|
||||||
uploadEvidence(input: UploadEvidenceInput!): UploadEvidencePayload!
|
requestEvidence(input: RequestEvidenceInput!): RequestEvidencePayload!
|
||||||
|
fulfillEvidence(input: FulfillEvidenceInput!): FulfillEvidencePayload!
|
||||||
|
createEvidence(input: CreateEvidenceInput!): CreateEvidencePayload!
|
||||||
deleteEvidence(input: DeleteEvidenceInput!): DeleteEvidencePayload!
|
deleteEvidence(input: DeleteEvidenceInput!): DeleteEvidencePayload!
|
||||||
|
|
||||||
# Policy mutations
|
# Policy mutations
|
||||||
@@ -939,12 +939,25 @@ input DeleteRiskMappingInput {
|
|||||||
mitigationId: ID!
|
mitigationId: ID!
|
||||||
}
|
}
|
||||||
|
|
||||||
input UploadEvidenceInput {
|
input RequestEvidenceInput {
|
||||||
taskId: ID!
|
taskId: ID!
|
||||||
name: String!
|
name: String!
|
||||||
|
type: EvidenceType!
|
||||||
|
description: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input FulfillEvidenceInput {
|
||||||
|
evidenceId: ID!
|
||||||
file: Upload
|
file: Upload
|
||||||
|
url: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input CreateEvidenceInput {
|
||||||
|
taskId: ID!
|
||||||
|
name: String!
|
||||||
type: EvidenceType!
|
type: EvidenceType!
|
||||||
url: String
|
url: String
|
||||||
|
file: Upload
|
||||||
description: String!
|
description: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1102,7 +1115,15 @@ type DeleteRiskMappingPayload {
|
|||||||
success: Boolean!
|
success: Boolean!
|
||||||
}
|
}
|
||||||
|
|
||||||
type UploadEvidencePayload {
|
type RequestEvidencePayload {
|
||||||
|
evidenceEdge: EvidenceEdge!
|
||||||
|
}
|
||||||
|
|
||||||
|
type FulfillEvidencePayload {
|
||||||
|
evidenceEdge: EvidenceEdge!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateEvidencePayload {
|
||||||
evidenceEdge: EvidenceEdge!
|
evidenceEdge: EvidenceEdge!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -68,6 +68,19 @@ type CreateControlMappingPayload struct {
|
|||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateEvidenceInput struct {
|
||||||
|
TaskID gid.GID `json:"taskId"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type coredata.EvidenceType `json:"type"`
|
||||||
|
URL *string `json:"url,omitempty"`
|
||||||
|
File *graphql.Upload `json:"file,omitempty"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateEvidencePayload struct {
|
||||||
|
EvidenceEdge *EvidenceEdge `json:"evidenceEdge"`
|
||||||
|
}
|
||||||
|
|
||||||
type CreateFrameworkInput struct {
|
type CreateFrameworkInput struct {
|
||||||
OrganizationID gid.GID `json:"organizationId"`
|
OrganizationID gid.GID `json:"organizationId"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -306,6 +319,16 @@ type FrameworkEdge struct {
|
|||||||
Node *Framework `json:"node"`
|
Node *Framework `json:"node"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FulfillEvidenceInput struct {
|
||||||
|
EvidenceID gid.GID `json:"evidenceId"`
|
||||||
|
File *graphql.Upload `json:"file,omitempty"`
|
||||||
|
URL *string `json:"url,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FulfillEvidencePayload struct {
|
||||||
|
EvidenceEdge *EvidenceEdge `json:"evidenceEdge"`
|
||||||
|
}
|
||||||
|
|
||||||
type ImportFrameworkInput struct {
|
type ImportFrameworkInput struct {
|
||||||
OrganizationID gid.GID `json:"organizationId"`
|
OrganizationID gid.GID `json:"organizationId"`
|
||||||
File graphql.Upload `json:"file"`
|
File graphql.Upload `json:"file"`
|
||||||
@@ -463,6 +486,17 @@ type RemoveUserPayload struct {
|
|||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RequestEvidenceInput struct {
|
||||||
|
TaskID gid.GID `json:"taskId"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type coredata.EvidenceType `json:"type"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequestEvidencePayload struct {
|
||||||
|
EvidenceEdge *EvidenceEdge `json:"evidenceEdge"`
|
||||||
|
}
|
||||||
|
|
||||||
type Risk struct {
|
type Risk struct {
|
||||||
ID gid.GID `json:"id"`
|
ID gid.GID `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -624,19 +658,6 @@ type UpdateVendorPayload struct {
|
|||||||
Vendor *Vendor `json:"vendor"`
|
Vendor *Vendor `json:"vendor"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UploadEvidenceInput struct {
|
|
||||||
TaskID gid.GID `json:"taskId"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
File *graphql.Upload `json:"file,omitempty"`
|
|
||||||
Type coredata.EvidenceType `json:"type"`
|
|
||||||
URL *string `json:"url,omitempty"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UploadEvidencePayload struct {
|
|
||||||
EvidenceEdge *EvidenceEdge `json:"evidenceEdge"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID gid.GID `json:"id"`
|
ID gid.GID `json:"id"`
|
||||||
FullName string `json:"fullName"`
|
FullName string `json:"fullName"`
|
||||||
|
|||||||
@@ -718,20 +718,62 @@ func (r *mutationResolver) DeleteRiskMapping(ctx context.Context, input types.De
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadEvidence is the resolver for the uploadEvidence field.
|
// RequestEvidence is the resolver for the requestEvidence field.
|
||||||
func (r *mutationResolver) UploadEvidence(ctx context.Context, input types.UploadEvidenceInput) (*types.UploadEvidencePayload, error) {
|
func (r *mutationResolver) RequestEvidence(ctx context.Context, input types.RequestEvidenceInput) (*types.RequestEvidencePayload, error) {
|
||||||
svc := r.GetTenantServiceIfAuthorized(ctx, input.TaskID.TenantID())
|
svc := r.GetTenantServiceIfAuthorized(ctx, input.TaskID.TenantID())
|
||||||
|
|
||||||
var url string
|
evidence, err := svc.Evidences.Request(
|
||||||
if input.URL != nil {
|
ctx,
|
||||||
url = *input.URL
|
probo.RequestEvidenceRequest{
|
||||||
|
TaskID: input.TaskID,
|
||||||
|
Name: input.Name,
|
||||||
|
Type: input.Type,
|
||||||
|
Description: input.Description,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("cannot request evidence: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return &types.RequestEvidencePayload{
|
||||||
|
EvidenceEdge: types.NewEvidenceEdge(evidence, coredata.EvidenceOrderFieldCreatedAt),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FulfillEvidence is the resolver for the fulfillEvidence field.
|
||||||
|
func (r *mutationResolver) FulfillEvidence(ctx context.Context, input types.FulfillEvidenceInput) (*types.FulfillEvidencePayload, error) {
|
||||||
|
svc := r.GetTenantServiceIfAuthorized(ctx, input.EvidenceID.TenantID())
|
||||||
|
|
||||||
|
req := probo.FulfilledEvidenceRequest{
|
||||||
|
EvidenceID: input.EvidenceID,
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.File != nil {
|
||||||
|
req.File = input.File.File
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.URL != nil {
|
||||||
|
req.URL = *input.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
evidence, err := svc.Evidences.Fulfill(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("cannot fulfill evidence: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.FulfillEvidencePayload{
|
||||||
|
EvidenceEdge: types.NewEvidenceEdge(evidence, coredata.EvidenceOrderFieldCreatedAt),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEvidence is the resolver for the createEvidence field.
|
||||||
|
func (r *mutationResolver) CreateEvidence(ctx context.Context, input types.CreateEvidenceInput) (*types.CreateEvidencePayload, error) {
|
||||||
|
svc := r.GetTenantServiceIfAuthorized(ctx, input.TaskID.TenantID())
|
||||||
|
|
||||||
req := probo.CreateEvidenceRequest{
|
req := probo.CreateEvidenceRequest{
|
||||||
TaskID: input.TaskID,
|
TaskID: input.TaskID,
|
||||||
Name: input.Name,
|
Name: input.Name,
|
||||||
Type: input.Type,
|
Type: input.Type,
|
||||||
URL: url,
|
|
||||||
Description: input.Description,
|
Description: input.Description,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -751,7 +793,7 @@ func (r *mutationResolver) UploadEvidence(ctx context.Context, input types.Uploa
|
|||||||
panic(fmt.Errorf("failed to create evidence: %w", err))
|
panic(fmt.Errorf("failed to create evidence: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &types.UploadEvidencePayload{
|
return &types.CreateEvidencePayload{
|
||||||
EvidenceEdge: types.NewEvidenceEdge(evidence, coredata.EvidenceOrderFieldCreatedAt),
|
EvidenceEdge: types.NewEvidenceEdge(evidence, coredata.EvidenceOrderFieldCreatedAt),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user