@@ -170,6 +170,7 @@ export const useCreateAudit = (connectionId: string) => {
|
|||||||
validUntil?: string;
|
validUntil?: string;
|
||||||
reportKey?: string;
|
reportKey?: string;
|
||||||
state?: string;
|
state?: string;
|
||||||
|
file?: File | null;
|
||||||
}) => {
|
}) => {
|
||||||
if (!input.organizationId) {
|
if (!input.organizationId) {
|
||||||
return alert(__("Failed to create audit: organization is required"));
|
return alert(__("Failed to create audit: organization is required"));
|
||||||
@@ -188,9 +189,11 @@ export const useCreateAudit = (connectionId: string) => {
|
|||||||
validUntil: input.validUntil,
|
validUntil: input.validUntil,
|
||||||
reportKey: input.reportKey,
|
reportKey: input.reportKey,
|
||||||
state: input.state || "NOT_STARTED",
|
state: input.state || "NOT_STARTED",
|
||||||
|
file: input.file ? null : undefined,
|
||||||
},
|
},
|
||||||
connections: [connectionId],
|
connections: [connectionId],
|
||||||
},
|
},
|
||||||
|
...(input.file ? { uploadables: { "input.file": input.file } } : {}),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,35 +23,15 @@ import {
|
|||||||
} from "@probo/ui";
|
} from "@probo/ui";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import { type Control, Controller } from "react-hook-form";
|
import { type Control, Controller } from "react-hook-form";
|
||||||
import { useLazyLoadQuery, useMutation } from "react-relay";
|
import { useLazyLoadQuery } from "react-relay";
|
||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import type { CreateAuditDialogFrameworksQuery } from "#/__generated__/core/CreateAuditDialogFrameworksQuery.graphql";
|
import type { CreateAuditDialogFrameworksQuery } from "#/__generated__/core/CreateAuditDialogFrameworksQuery.graphql";
|
||||||
import type { CreateAuditDialogUploadReportMutation } from "#/__generated__/core/CreateAuditDialogUploadReportMutation.graphql";
|
|
||||||
import { ControlledField } from "#/components/form/ControlledField";
|
import { ControlledField } from "#/components/form/ControlledField";
|
||||||
import { useCreateAudit } from "#/hooks/graph/AuditGraph";
|
import { useCreateAudit } from "#/hooks/graph/AuditGraph";
|
||||||
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
|
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
|
||||||
|
|
||||||
const uploadAuditReportMutation = graphql`
|
|
||||||
mutation CreateAuditDialogUploadReportMutation(
|
|
||||||
$input: UploadAuditReportInput!
|
|
||||||
) {
|
|
||||||
uploadAuditReport(input: $input) {
|
|
||||||
audit {
|
|
||||||
id
|
|
||||||
report {
|
|
||||||
id
|
|
||||||
filename
|
|
||||||
downloadUrl
|
|
||||||
createdAt
|
|
||||||
}
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const frameworksQuery = graphql`
|
const frameworksQuery = graphql`
|
||||||
query CreateAuditDialogFrameworksQuery($organizationId: ID!) {
|
query CreateAuditDialogFrameworksQuery($organizationId: ID!) {
|
||||||
organization: node(id: $organizationId) {
|
organization: node(id: $organizationId) {
|
||||||
@@ -115,68 +95,29 @@ export function CreateAuditDialog({
|
|||||||
const internalRef = useDialogRef();
|
const internalRef = useDialogRef();
|
||||||
const ref = externalRef ?? internalRef;
|
const ref = externalRef ?? internalRef;
|
||||||
const createAudit = useCreateAudit(connection);
|
const createAudit = useCreateAudit(connection);
|
||||||
const [uploadMutate] = useMutation<CreateAuditDialogUploadReportMutation>(uploadAuditReportMutation);
|
|
||||||
|
|
||||||
const onSubmit = async (data: z.infer<typeof schema>) => {
|
const onSubmit = async (data: z.infer<typeof schema>) => {
|
||||||
try {
|
try {
|
||||||
const response = await createAudit({
|
await createAudit({
|
||||||
organizationId,
|
organizationId,
|
||||||
frameworkId: data.frameworkId,
|
frameworkId: data.frameworkId,
|
||||||
name: data.name || null,
|
name: data.name || null,
|
||||||
validFrom: formatDatetime(data.validFrom),
|
validFrom: formatDatetime(data.validFrom),
|
||||||
validUntil: formatDatetime(data.validUntil),
|
validUntil: formatDatetime(data.validUntil),
|
||||||
state: data.state,
|
state: data.state,
|
||||||
|
file: file ?? null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const auditId = (response as { createAudit: { auditEdge: { node: { id: string } } } })
|
|
||||||
.createAudit.auditEdge.node.id;
|
|
||||||
|
|
||||||
if (file && auditId) {
|
|
||||||
try {
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
|
||||||
uploadMutate({
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
auditId,
|
|
||||||
file: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
uploadables: {
|
|
||||||
"input.file": file,
|
|
||||||
},
|
|
||||||
onCompleted: (_response, errors) => {
|
|
||||||
if (errors) {
|
|
||||||
reject(errors);
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onError: error => reject(error),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
toast({
|
|
||||||
title: __("Success"),
|
|
||||||
description: __("Audit created and report uploaded successfully"),
|
|
||||||
variant: "success",
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
toast({
|
|
||||||
title: __("Warning"),
|
|
||||||
description: __("Audit created but report upload failed. You can upload the report from the audit detail page."),
|
|
||||||
variant: "warning",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
toast({
|
|
||||||
title: __("Success"),
|
|
||||||
description: __("Audit created successfully"),
|
|
||||||
variant: "success",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
ref.current?.close();
|
ref.current?.close();
|
||||||
reset();
|
reset();
|
||||||
onClose?.();
|
onClose?.();
|
||||||
|
toast({
|
||||||
|
title: __("Success"),
|
||||||
|
description: file
|
||||||
|
? __("Audit created and report uploaded successfully")
|
||||||
|
: __("Audit created successfully"),
|
||||||
|
variant: "success",
|
||||||
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({
|
toast({
|
||||||
title: __("Error"),
|
title: __("Error"),
|
||||||
|
|||||||
@@ -4459,6 +4459,7 @@ input CreateAuditInput {
|
|||||||
validUntil: Datetime
|
validUntil: Datetime
|
||||||
state: AuditState
|
state: AuditState
|
||||||
trustCenterVisibility: TrustCenterVisibility
|
trustCenterVisibility: TrustCenterVisibility
|
||||||
|
file: Upload
|
||||||
}
|
}
|
||||||
|
|
||||||
input UpdateAuditInput {
|
input UpdateAuditInput {
|
||||||
|
|||||||
@@ -5338,6 +5338,27 @@ func (r *mutationResolver) CreateAudit(ctx context.Context, input types.CreateAu
|
|||||||
return nil, gqlutils.Internal(ctx)
|
return nil, gqlutils.Internal(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if input.File != nil {
|
||||||
|
uploadReq := probo.UploadAuditReportRequest{
|
||||||
|
AuditID: audit.ID,
|
||||||
|
File: probo.File{
|
||||||
|
Content: input.File.File,
|
||||||
|
Filename: input.File.Filename,
|
||||||
|
Size: input.File.Size,
|
||||||
|
ContentType: input.File.ContentType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
audit, err = prb.Audits.UploadReport(ctx, uploadReq)
|
||||||
|
if err != nil {
|
||||||
|
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
|
||||||
|
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
|
||||||
|
}
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot upload audit report", log.Error(err))
|
||||||
|
return nil, gqlutils.Internal(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &types.CreateAuditPayload{
|
return &types.CreateAuditPayload{
|
||||||
AuditEdge: types.NewAuditEdge(audit, coredata.AuditOrderFieldCreatedAt),
|
AuditEdge: types.NewAuditEdge(audit, coredata.AuditOrderFieldCreatedAt),
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
Reference in New Issue
Block a user