Add file upload for task evidence

The feature allows users to attach evidence files to control tasks by
clicking an upload icon, enhancing compliance documentation
capabilities.

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-02-28 11:01:19 +01:00
parent fc5a97cb64
commit cfabe3ee80
6 changed files with 659 additions and 6 deletions

View File

@@ -1,4 +1,4 @@
import { Suspense, useEffect, useState } from "react"; import { Suspense, useEffect, useState, useRef } from "react";
import { useParams, useNavigate } from "react-router"; import { useParams, useNavigate } from "react-router";
import { import {
graphql, graphql,
@@ -7,7 +7,7 @@ import {
useQueryLoader, useQueryLoader,
useMutation, useMutation,
} from "react-relay"; } from "react-relay";
import { CheckCircle2, Plus, Trash2 } from "lucide-react"; import { CheckCircle2, Plus, Trash2, Upload } from "lucide-react";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } 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";
@@ -21,6 +21,7 @@ import {
DialogTitle, DialogTitle,
DialogTrigger, DialogTrigger,
} from "@/components/ui/dialog"; } from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import type { ControlOverviewPageQuery as ControlOverviewPageQueryType } from "./__generated__/ControlOverviewPageQuery.graphql"; import type { ControlOverviewPageQuery as ControlOverviewPageQueryType } from "./__generated__/ControlOverviewPageQuery.graphql";
@@ -95,6 +96,26 @@ const deleteTaskMutation = graphql`
} }
`; `;
const uploadEvidenceMutation = graphql`
mutation ControlOverviewPageUploadEvidenceMutation(
$input: UploadEvidenceInput!
$connections: [ID!]!
) {
uploadEvidence(input: $input) {
evidenceEdge @appendEdge(connections: $connections) {
node {
id
fileUrl
mimeType
size
state
createdAt
}
}
}
}
`;
function ControlOverviewPageContent({ function ControlOverviewPageContent({
queryRef, queryRef,
}: { }: {
@@ -102,19 +123,23 @@ function ControlOverviewPageContent({
}) { }) {
const data = usePreloadedQuery<ControlOverviewPageQueryType>( const data = usePreloadedQuery<ControlOverviewPageQueryType>(
controlOverviewPageQuery, controlOverviewPageQuery,
queryRef, queryRef
); );
const { toast } = useToast(); const { toast } = useToast();
const { organizationId, frameworkId, controlId } = useParams(); const { organizationId, frameworkId, controlId } = useParams();
const navigate = useNavigate(); const navigate = useNavigate();
const [updateTaskState] = const [updateTaskState] =
useMutation<ControlOverviewPageUpdateTaskStateMutationType>( useMutation<ControlOverviewPageUpdateTaskStateMutationType>(
updateTaskStateMutation, updateTaskStateMutation
); );
const [createTask] = const [createTask] =
useMutation<ControlOverviewPageCreateTaskMutationType>(createTaskMutation); useMutation<ControlOverviewPageCreateTaskMutationType>(createTaskMutation);
const [deleteTask] = const [deleteTask] =
useMutation<ControlOverviewPageDeleteTaskMutationType>(deleteTaskMutation); useMutation<ControlOverviewPageDeleteTaskMutationType>(deleteTaskMutation);
const [uploadEvidence] =
useMutation<ControlOverviewPageUploadEvidenceMutationType>(
uploadEvidenceMutation
);
const control = data.control; const control = data.control;
const tasks = control?.tasks?.edges.map((edge) => edge?.node) ?? []; const tasks = control?.tasks?.edges.map((edge) => edge?.node) ?? [];
@@ -128,6 +153,14 @@ function ControlOverviewPageContent({
name: string; name: string;
} | null>(null); } | null>(null);
const [isUploadEvidenceOpen, setIsUploadEvidenceOpen] = useState(false);
const [taskForEvidence, setTaskForEvidence] = useState<{
id: string;
name: string;
} | null>(null);
const [evidenceName, setEvidenceName] = useState("");
const fileInputRef = useRef<HTMLInputElement>(null);
const handleTaskClick = (taskId: string, currentState: string) => { const handleTaskClick = (taskId: string, currentState: string) => {
const newState = currentState === "DONE" ? "TODO" : "DONE"; const newState = currentState === "DONE" ? "TODO" : "DONE";
@@ -248,10 +281,56 @@ function ControlOverviewPageContent({
const handleEditControl = () => { const handleEditControl = () => {
navigate( navigate(
`/organizations/${organizationId}/frameworks/${frameworkId}/controls/${controlId}/update`, `/organizations/${organizationId}/frameworks/${frameworkId}/controls/${controlId}/update`
); );
}; };
const handleUploadEvidence = (taskId: string, taskName: string) => {
setTaskForEvidence({ id: taskId, name: taskName });
setIsUploadEvidenceOpen(true);
};
const confirmUploadEvidence = (event: React.FormEvent) => {
event.preventDefault();
if (!taskForEvidence || !fileInputRef.current?.files?.[0]) return;
const file = fileInputRef.current.files[0];
uploadEvidence({
variables: {
input: {
taskId: taskForEvidence.id,
name: evidenceName || file.name,
file: null,
},
connections: [`client:${taskForEvidence.id}`],
},
uploadables: {
"input.file": file,
},
onCompleted: () => {
toast({
title: "Evidence uploaded",
description: "Evidence has been uploaded successfully.",
});
setIsUploadEvidenceOpen(false);
setTaskForEvidence(null);
setEvidenceName("");
if (fileInputRef.current) {
fileInputRef.current.value = "";
}
},
onError: (error) => {
toast({
title: "Error uploading evidence",
description: error.message,
variant: "destructive",
});
},
});
};
return ( return (
<> <>
<Helmet> <Helmet>
@@ -413,6 +492,17 @@ function ControlOverviewPageContent({
</div> </div>
<div className="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity"> <div className="flex items-center gap-2 opacity-0 group-hover:opacity-100 transition-opacity">
<div className="text-gray-400 text-sm">06.00 - 07.30</div> <div className="text-gray-400 text-sm">06.00 - 07.30</div>
<button
className="text-gray-400 hover:text-blue-600"
onClick={(e) => {
e.stopPropagation();
if (task?.id && task?.name) {
handleUploadEvidence(task.id, task.name);
}
}}
>
<Upload className="w-4 h-4" />
</button>
<button <button
className="text-gray-400 hover:text-red-600" className="text-gray-400 hover:text-red-600"
onClick={(e) => { onClick={(e) => {
@@ -460,6 +550,56 @@ function ControlOverviewPageContent({
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
{/* Upload Evidence Dialog */}
<Dialog
open={isUploadEvidenceOpen}
onOpenChange={setIsUploadEvidenceOpen}
>
<DialogContent>
<DialogHeader>
<DialogTitle>Upload Evidence</DialogTitle>
<DialogDescription>
Upload evidence for the task &quot;{taskForEvidence?.name}
&quot;.
</DialogDescription>
</DialogHeader>
<form onSubmit={confirmUploadEvidence}>
<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="evidence-name">
Evidence Name (Optional)
</Label>
<Input
id="evidence-name"
placeholder="Enter a name for this evidence"
value={evidenceName}
onChange={(e) => setEvidenceName(e.target.value)}
/>
</div>
<div className="space-y-2">
<Label htmlFor="evidence-file">File</Label>
<Input
id="evidence-file"
type="file"
ref={fileInputRef}
required
/>
</div>
</div>
<DialogFooter>
<Button
variant="outline"
type="button"
onClick={() => setIsUploadEvidenceOpen(false)}
>
Cancel
</Button>
<Button type="submit">Upload</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</div> </div>
</> </>
); );
@@ -489,7 +629,7 @@ function ControlOverviewPageFallback() {
export default function ControlOverviewPage() { export default function ControlOverviewPage() {
const { controlId } = useParams(); const { controlId } = useParams();
const [queryRef, loadQuery] = useQueryLoader<ControlOverviewPageQueryType>( const [queryRef, loadQuery] = useQueryLoader<ControlOverviewPageQueryType>(
controlOverviewPageQuery, controlOverviewPageQuery
); );
useEffect(() => { useEffect(() => {

View File

@@ -0,0 +1,201 @@
/**
* @generated SignedSource<<09af19b5a7657e04606a42962db9db2c>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type EvidenceState = "EXPIRED" | "INVALID" | "VALID";
export type UploadEvidenceInput = {
file: any;
name: string;
taskId: string;
};
export type ControlOverviewPageUploadEvidenceMutation$variables = {
connections: ReadonlyArray<string>;
input: UploadEvidenceInput;
};
export type ControlOverviewPageUploadEvidenceMutation$data = {
readonly uploadEvidence: {
readonly evidenceEdge: {
readonly node: {
readonly createdAt: any;
readonly fileUrl: string;
readonly id: string;
readonly mimeType: string;
readonly size: number;
readonly state: EvidenceState;
};
};
};
};
export type ControlOverviewPageUploadEvidenceMutation = {
response: ControlOverviewPageUploadEvidenceMutation$data;
variables: ControlOverviewPageUploadEvidenceMutation$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": "fileUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "mimeType",
"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": "ControlOverviewPageUploadEvidenceMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "UploadEvidencePayload",
"kind": "LinkedField",
"name": "uploadEvidence",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "ControlOverviewPageUploadEvidenceMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "UploadEvidencePayload",
"kind": "LinkedField",
"name": "uploadEvidence",
"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": "76c2ba38a9ba243840027c997b01b51b",
"id": null,
"metadata": {},
"name": "ControlOverviewPageUploadEvidenceMutation",
"operationKind": "mutation",
"text": "mutation ControlOverviewPageUploadEvidenceMutation(\n $input: UploadEvidenceInput!\n) {\n uploadEvidence(input: $input) {\n evidenceEdge {\n node {\n id\n fileUrl\n mimeType\n size\n state\n createdAt\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "b43bb6fc3d2a70fd8f81ccfb2532f067";
export default node;

View File

@@ -401,6 +401,7 @@ type Mutation {
createControl(input: CreateControlInput!): CreateControlPayload! createControl(input: CreateControlInput!): CreateControlPayload!
updateFramework(input: UpdateFrameworkInput!): UpdateFrameworkPayload! updateFramework(input: UpdateFrameworkInput!): UpdateFrameworkPayload!
updateControl(input: UpdateControlInput!): UpdateControlPayload! updateControl(input: UpdateControlInput!): UpdateControlPayload!
uploadEvidence(input: UploadEvidenceInput!): UploadEvidencePayload!
} }
input CreateVendorInput { input CreateVendorInput {
@@ -600,3 +601,13 @@ input UpdateControlInput {
type UpdateControlPayload { type UpdateControlPayload {
control: Control! control: Control!
} }
input UploadEvidenceInput {
taskId: ID!
name: String!
file: Upload!
}
type UploadEvidencePayload {
evidenceEdge: EvidenceEdge!
}

View File

@@ -214,6 +214,7 @@ type ComplexityRoot struct {
UpdatePeople func(childComplexity int, input types.UpdatePeopleInput) int UpdatePeople func(childComplexity int, input types.UpdatePeopleInput) int
UpdateTaskState func(childComplexity int, input types.UpdateTaskStateInput) int UpdateTaskState func(childComplexity int, input types.UpdateTaskStateInput) int
UpdateVendor func(childComplexity int, input types.UpdateVendorInput) int UpdateVendor func(childComplexity int, input types.UpdateVendorInput) int
UploadEvidence func(childComplexity int, input types.UploadEvidenceInput) int
} }
Organization struct { Organization struct {
@@ -335,6 +336,10 @@ type ComplexityRoot struct {
Vendor func(childComplexity int) int Vendor func(childComplexity int) int
} }
UploadEvidencePayload struct {
EvidenceEdge func(childComplexity int) int
}
User struct { User struct {
CreatedAt func(childComplexity int) int CreatedAt func(childComplexity int) int
Email func(childComplexity int) int Email func(childComplexity int) int
@@ -397,6 +402,7 @@ type MutationResolver interface {
CreateControl(ctx context.Context, input types.CreateControlInput) (*types.CreateControlPayload, error) CreateControl(ctx context.Context, input types.CreateControlInput) (*types.CreateControlPayload, error)
UpdateFramework(ctx context.Context, input types.UpdateFrameworkInput) (*types.UpdateFrameworkPayload, error) UpdateFramework(ctx context.Context, input types.UpdateFrameworkInput) (*types.UpdateFrameworkPayload, error)
UpdateControl(ctx context.Context, input types.UpdateControlInput) (*types.UpdateControlPayload, error) UpdateControl(ctx context.Context, input types.UpdateControlInput) (*types.UpdateControlPayload, error)
UploadEvidence(ctx context.Context, input types.UploadEvidenceInput) (*types.UploadEvidencePayload, error)
} }
type OrganizationResolver interface { type OrganizationResolver interface {
Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error) Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error)
@@ -1103,6 +1109,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.UpdateVendor(childComplexity, args["input"].(types.UpdateVendorInput)), true return e.complexity.Mutation.UpdateVendor(childComplexity, args["input"].(types.UpdateVendorInput)), true
case "Mutation.uploadEvidence":
if e.complexity.Mutation.UploadEvidence == nil {
break
}
args, err := ec.field_Mutation_uploadEvidence_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.UploadEvidence(childComplexity, args["input"].(types.UploadEvidenceInput)), true
case "Organization.createdAt": case "Organization.createdAt":
if e.complexity.Organization.CreatedAt == nil { if e.complexity.Organization.CreatedAt == nil {
break break
@@ -1546,6 +1564,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.UpdateVendorPayload.Vendor(childComplexity), true return e.complexity.UpdateVendorPayload.Vendor(childComplexity), true
case "UploadEvidencePayload.evidenceEdge":
if e.complexity.UploadEvidencePayload.EvidenceEdge == nil {
break
}
return e.complexity.UploadEvidencePayload.EvidenceEdge(childComplexity), true
case "User.createdAt": case "User.createdAt":
if e.complexity.User.CreatedAt == nil { if e.complexity.User.CreatedAt == nil {
break break
@@ -1735,6 +1760,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputUpdatePeopleInput, ec.unmarshalInputUpdatePeopleInput,
ec.unmarshalInputUpdateTaskStateInput, ec.unmarshalInputUpdateTaskStateInput,
ec.unmarshalInputUpdateVendorInput, ec.unmarshalInputUpdateVendorInput,
ec.unmarshalInputUploadEvidenceInput,
) )
first := true first := true
@@ -2235,6 +2261,7 @@ type Mutation {
createControl(input: CreateControlInput!): CreateControlPayload! createControl(input: CreateControlInput!): CreateControlPayload!
updateFramework(input: UpdateFrameworkInput!): UpdateFrameworkPayload! updateFramework(input: UpdateFrameworkInput!): UpdateFrameworkPayload!
updateControl(input: UpdateControlInput!): UpdateControlPayload! updateControl(input: UpdateControlInput!): UpdateControlPayload!
uploadEvidence(input: UploadEvidenceInput!): UploadEvidencePayload!
} }
input CreateVendorInput { input CreateVendorInput {
@@ -2434,6 +2461,16 @@ input UpdateControlInput {
type UpdateControlPayload { type UpdateControlPayload {
control: Control! control: Control!
} }
input UploadEvidenceInput {
taskId: ID!
name: String!
file: Upload!
}
type UploadEvidencePayload {
evidenceEdge: EvidenceEdge!
}
`, BuiltIn: false}, `, BuiltIn: false},
} }
var parsedSchema = gqlparser.MustLoadSchema(sources...) var parsedSchema = gqlparser.MustLoadSchema(sources...)
@@ -3095,6 +3132,29 @@ func (ec *executionContext) field_Mutation_updateVendor_argsInput(
return zeroVal, nil return zeroVal, nil
} }
func (ec *executionContext) field_Mutation_uploadEvidence_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := ec.field_Mutation_uploadEvidence_argsInput(ctx, rawArgs)
if err != nil {
return nil, err
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_uploadEvidence_argsInput(
ctx context.Context,
rawArgs map[string]any,
) (types.UploadEvidenceInput, error) {
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input"))
if tmp, ok := rawArgs["input"]; ok {
return ec.unmarshalNUploadEvidenceInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐUploadEvidenceInput(ctx, tmp)
}
var zeroVal types.UploadEvidenceInput
return zeroVal, nil
}
func (ec *executionContext) field_Organization_frameworks_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { func (ec *executionContext) field_Organization_frameworks_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error var err error
args := map[string]any{} args := map[string]any{}
@@ -7140,6 +7200,53 @@ func (ec *executionContext) fieldContext_Mutation_updateControl(ctx context.Cont
return fc, nil return fc, nil
} }
func (ec *executionContext) _Mutation_uploadEvidence(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_uploadEvidence(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().UploadEvidence(rctx, fc.Args["input"].(types.UploadEvidenceInput))
})
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.UploadEvidencePayload)
fc.Result = res
return ec.marshalNUploadEvidencePayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐUploadEvidencePayload(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Mutation_uploadEvidence(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 "evidenceEdge":
return ec.fieldContext_UploadEvidencePayload_evidenceEdge(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type UploadEvidencePayload", field.Name)
},
}
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Mutation_uploadEvidence_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) { 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) fc, err := ec.fieldContext_Organization_id(ctx, field)
if err != nil { if err != nil {
@@ -9789,6 +9896,50 @@ func (ec *executionContext) fieldContext_UpdateVendorPayload_vendor(_ context.Co
return fc, nil return fc, nil
} }
func (ec *executionContext) _UploadEvidencePayload_evidenceEdge(ctx context.Context, field graphql.CollectedField, obj *types.UploadEvidencePayload) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_UploadEvidencePayload_evidenceEdge(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return obj.EvidenceEdge, 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.EvidenceEdge)
fc.Result = res
return ec.marshalNEvidenceEdge2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐEvidenceEdge(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_UploadEvidencePayload_evidenceEdge(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "UploadEvidencePayload",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "cursor":
return ec.fieldContext_EvidenceEdge_cursor(ctx, field)
case "node":
return ec.fieldContext_EvidenceEdge_node(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type EvidenceEdge", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _User_id(ctx context.Context, field graphql.CollectedField, obj *types.User) (ret graphql.Marshaler) { func (ec *executionContext) _User_id(ctx context.Context, field graphql.CollectedField, obj *types.User) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_User_id(ctx, field) fc, err := ec.fieldContext_User_id(ctx, field)
if err != nil { if err != nil {
@@ -12970,6 +13121,47 @@ func (ec *executionContext) unmarshalInputUpdateVendorInput(ctx context.Context,
return it, nil return it, nil
} }
func (ec *executionContext) unmarshalInputUploadEvidenceInput(ctx context.Context, obj any) (types.UploadEvidenceInput, error) {
var it types.UploadEvidenceInput
asMap := map[string]any{}
for k, v := range obj.(map[string]any) {
asMap[k] = v
}
fieldsInOrder := [...]string{"taskId", "name", "file"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "taskId":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("taskId"))
data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v)
if err != nil {
return it, err
}
it.TaskID = data
case "name":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name"))
data, err := ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
it.Name = data
case "file":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("file"))
data, err := ec.unmarshalNUpload2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚐUpload(ctx, v)
if err != nil {
return it, err
}
it.File = data
}
}
return it, nil
}
// endregion **************************** input.gotpl ***************************** // endregion **************************** input.gotpl *****************************
// region ************************** interface.gotpl *************************** // region ************************** interface.gotpl ***************************
@@ -14444,6 +14636,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ out.Invalids++
} }
case "uploadEvidence":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_uploadEvidence(ctx, field)
})
if out.Values[i] == graphql.Null {
out.Invalids++
}
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }
@@ -15600,6 +15799,45 @@ func (ec *executionContext) _UpdateVendorPayload(ctx context.Context, sel ast.Se
return out return out
} }
var uploadEvidencePayloadImplementors = []string{"UploadEvidencePayload"}
func (ec *executionContext) _UploadEvidencePayload(ctx context.Context, sel ast.SelectionSet, obj *types.UploadEvidencePayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, uploadEvidencePayloadImplementors)
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("UploadEvidencePayload")
case "evidenceEdge":
out.Values[i] = ec._UploadEvidencePayload_evidenceEdge(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 userImplementors = []string{"User", "Node"} var userImplementors = []string{"User", "Node"}
func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj *types.User) graphql.Marshaler { func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj *types.User) graphql.Marshaler {
@@ -17438,6 +17676,40 @@ func (ec *executionContext) marshalNUpdateVendorPayload2ᚖgithubᚗcomᚋgetpro
return ec._UpdateVendorPayload(ctx, sel, v) return ec._UpdateVendorPayload(ctx, sel, v)
} }
func (ec *executionContext) unmarshalNUpload2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚐUpload(ctx context.Context, v any) (graphql.Upload, error) {
res, err := graphql.UnmarshalUpload(v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNUpload2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚐUpload(ctx context.Context, sel ast.SelectionSet, v graphql.Upload) graphql.Marshaler {
res := graphql.MarshalUpload(v)
if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
}
}
return res
}
func (ec *executionContext) unmarshalNUploadEvidenceInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐUploadEvidenceInput(ctx context.Context, v any) (types.UploadEvidenceInput, error) {
res, err := ec.unmarshalInputUploadEvidenceInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNUploadEvidencePayload2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐUploadEvidencePayload(ctx context.Context, sel ast.SelectionSet, v types.UploadEvidencePayload) graphql.Marshaler {
return ec._UploadEvidencePayload(ctx, sel, &v)
}
func (ec *executionContext) marshalNUploadEvidencePayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐUploadEvidencePayload(ctx context.Context, sel ast.SelectionSet, v *types.UploadEvidencePayload) 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._UploadEvidencePayload(ctx, sel, v)
}
func (ec *executionContext) marshalNUser2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐUser(ctx context.Context, sel ast.SelectionSet, v types.User) graphql.Marshaler { func (ec *executionContext) marshalNUser2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐUser(ctx context.Context, sel ast.SelectionSet, v types.User) graphql.Marshaler {
return ec._User(ctx, sel, &v) return ec._User(ctx, sel, &v)
} }

View File

@@ -5,6 +5,7 @@ package types
import ( import (
"time" "time"
"github.com/99designs/gqlgen/graphql"
"github.com/getprobo/probo/pkg/gid" "github.com/getprobo/probo/pkg/gid"
"github.com/getprobo/probo/pkg/page" "github.com/getprobo/probo/pkg/page"
"github.com/getprobo/probo/pkg/probo/coredata" "github.com/getprobo/probo/pkg/probo/coredata"
@@ -399,6 +400,16 @@ 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"`
}
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"`

View File

@@ -334,6 +334,24 @@ func (r *mutationResolver) UpdateControl(ctx context.Context, input types.Update
}, nil }, nil
} }
// UploadEvidence is the resolver for the uploadEvidence field.
func (r *mutationResolver) UploadEvidence(ctx context.Context, input types.UploadEvidenceInput) (*types.UploadEvidencePayload, error) {
req := probo.CreateEvidenceRequest{
TaskID: input.TaskID,
Name: input.Name,
File: input.File.File,
}
evidence, err := r.proboSvc.CreateEvidence(ctx, req)
if err != nil {
return nil, fmt.Errorf("failed to create evidence: %w", err)
}
return &types.UploadEvidencePayload{
EvidenceEdge: types.NewEvidenceEdge(evidence),
}, nil
}
// Frameworks is the resolver for the frameworks field. // Frameworks is the resolver for the frameworks field.
func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error) { func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error) {
cursor := types.NewCursor(first, after, last, before) cursor := types.NewCursor(first, after, last, before)