@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<bc0fdc02fb8b596386e290ae0cf0b8b8>>
|
||||
* @generated SignedSource<<e7dfc0de7524eaa22ca235ac0b6e3011>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -11,7 +11,7 @@
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type OrderDirection = "ASC" | "DESC";
|
||||
export type PeopleOrderField = "CREATED_AT" | "FULL_NAME";
|
||||
export type PeopleOrderField = "CREATED_AT" | "FULL_NAME" | "KIND";
|
||||
export type PeopleOrder = {
|
||||
direction: OrderDirection;
|
||||
field: PeopleOrderField;
|
||||
|
||||
@@ -26,8 +26,13 @@ import { Navigate, Outlet, useNavigate, useParams } from "react-router";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import type { FrameworkGraphNodeQuery } from "/hooks/graph/__generated__/FrameworkGraphNodeQuery.graphql";
|
||||
import type { FrameworkDetailPageFragment$key } from "./__generated__/FrameworkDetailPageFragment.graphql";
|
||||
import type {
|
||||
FrameworkDetailPageGenerateFrameworkStateOfApplicabilityMutation,
|
||||
FrameworkDetailPageGenerateFrameworkStateOfApplicabilityMutation$data,
|
||||
} from "./__generated__/FrameworkDetailPageGenerateFrameworkStateOfApplicabilityMutation.graphql";
|
||||
import { FrameworkFormDialog } from "./dialogs/FrameworkFormDialog";
|
||||
import { FrameworkControlDialog } from "./dialogs/FrameworkControlDialog";
|
||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||
|
||||
const frameworkDetailFragment = graphql`
|
||||
fragment FrameworkDetailPageFragment on Framework {
|
||||
@@ -47,6 +52,18 @@ const frameworkDetailFragment = graphql`
|
||||
}
|
||||
`;
|
||||
|
||||
const generateFrameworkStateOfApplicabilityMutation = graphql`
|
||||
mutation FrameworkDetailPageGenerateFrameworkStateOfApplicabilityMutation(
|
||||
$frameworkId: ID!
|
||||
) {
|
||||
generateFrameworkStateOfApplicability(
|
||||
input: { frameworkId: $frameworkId }
|
||||
) {
|
||||
downloadUrl
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
queryRef: PreloadedQuery<FrameworkGraphNodeQuery>;
|
||||
};
|
||||
@@ -71,6 +88,16 @@ export default function FrameworkDetailPage(props: Props) {
|
||||
ConnectionHandler.getConnectionID(organizationId, connectionListKey)!
|
||||
);
|
||||
|
||||
const [generateFrameworkStateOfApplicability] =
|
||||
useMutationWithToasts<FrameworkDetailPageGenerateFrameworkStateOfApplicabilityMutation>(
|
||||
generateFrameworkStateOfApplicabilityMutation,
|
||||
{
|
||||
errorMessage: "Failed to generate framework state of applicability",
|
||||
successMessage:
|
||||
"Framework state of applicability generated successfully",
|
||||
}
|
||||
);
|
||||
|
||||
usePageTitle(`${framework.name} | ${selectedControl?.sectionTitle}`);
|
||||
const onDelete = () => {
|
||||
deleteFramework({
|
||||
@@ -107,6 +134,29 @@ export default function FrameworkDetailPage(props: Props) {
|
||||
</Button>
|
||||
</FrameworkFormDialog>
|
||||
<ActionDropdown variant="secondary">
|
||||
<DropdownItem
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
generateFrameworkStateOfApplicability({
|
||||
variables: { frameworkId: framework.id },
|
||||
onCompleted: (
|
||||
data: FrameworkDetailPageGenerateFrameworkStateOfApplicabilityMutation$data
|
||||
) => {
|
||||
if (data.generateFrameworkStateOfApplicability.downloadUrl) {
|
||||
const link = document.createElement("a");
|
||||
link.href =
|
||||
data.generateFrameworkStateOfApplicability.downloadUrl;
|
||||
link.download = `${framework.name}-SOA.pdf`; // You can adjust the filename as needed
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{__("Download SOA")}
|
||||
</DropdownItem>
|
||||
<DropdownItem icon={IconTrashCan} variant="danger" onClick={onDelete}>
|
||||
{__("Delete")}
|
||||
</DropdownItem>
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* @generated SignedSource<<bcdaed0c84805f387ee7d1de8ece0068>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type FrameworkDetailPageGenerateFrameworkStateOfApplicabilityMutation$variables = {
|
||||
frameworkId: string;
|
||||
};
|
||||
export type FrameworkDetailPageGenerateFrameworkStateOfApplicabilityMutation$data = {
|
||||
readonly generateFrameworkStateOfApplicability: {
|
||||
readonly downloadUrl: string;
|
||||
};
|
||||
};
|
||||
export type FrameworkDetailPageGenerateFrameworkStateOfApplicabilityMutation = {
|
||||
response: FrameworkDetailPageGenerateFrameworkStateOfApplicabilityMutation$data;
|
||||
variables: FrameworkDetailPageGenerateFrameworkStateOfApplicabilityMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "frameworkId"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"fields": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "frameworkId",
|
||||
"variableName": "frameworkId"
|
||||
}
|
||||
],
|
||||
"kind": "ObjectValue",
|
||||
"name": "input"
|
||||
}
|
||||
],
|
||||
"concreteType": "GenerateFrameworkStateOfApplicabilityPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "generateFrameworkStateOfApplicability",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "downloadUrl",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "FrameworkDetailPageGenerateFrameworkStateOfApplicabilityMutation",
|
||||
"selections": (v1/*: any*/),
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "FrameworkDetailPageGenerateFrameworkStateOfApplicabilityMutation",
|
||||
"selections": (v1/*: any*/)
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "338cbffe84fb19cdf46c97b739216342",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "FrameworkDetailPageGenerateFrameworkStateOfApplicabilityMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation FrameworkDetailPageGenerateFrameworkStateOfApplicabilityMutation(\n $frameworkId: ID!\n) {\n generateFrameworkStateOfApplicability(input: {frameworkId: $frameworkId}) {\n downloadUrl\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "f5c0a7d2bebedecc3de7d55d52f06cd8";
|
||||
|
||||
export default node;
|
||||
6
go.mod
6
go.mod
@@ -18,6 +18,7 @@ require (
|
||||
github.com/openai/openai-go v1.1.0
|
||||
github.com/prometheus/client_golang v1.22.0
|
||||
github.com/vektah/gqlparser/v2 v2.5.27
|
||||
github.com/xuri/excelize/v2 v2.9.1
|
||||
go.gearno.de/crypto/uuid v0.1.0
|
||||
go.gearno.de/kit v0.0.0-20250623163305-45b4f6905899
|
||||
go.gearno.de/x/ref v0.0.0-20240502200927-d74926fcb14c
|
||||
@@ -61,6 +62,8 @@ require (
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.64.0 // indirect
|
||||
github.com/prometheus/procfs v0.16.1 // indirect
|
||||
github.com/richardlehane/mscfb v1.0.4 // indirect
|
||||
github.com/richardlehane/msoleps v1.0.4 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/sosodev/duration v1.3.1 // indirect
|
||||
@@ -69,8 +72,11 @@ require (
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/tidwall/sjson v1.2.5 // indirect
|
||||
github.com/tiendc/go-deepcopy v1.6.0 // indirect
|
||||
github.com/urfave/cli/v2 v2.27.6 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
github.com/xuri/efp v0.0.1 // indirect
|
||||
github.com/xuri/nfp v0.0.1 // indirect
|
||||
go.gearno.de/x/panicf v0.1.1 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
|
||||
|
||||
15
go.sum
15
go.sum
@@ -120,6 +120,11 @@ github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQP
|
||||
github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
||||
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||
github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
|
||||
github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
|
||||
github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
|
||||
github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
@@ -150,12 +155,20 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
|
||||
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
|
||||
github.com/tiendc/go-deepcopy v1.6.0 h1:0UtfV/imoCwlLxVsyfUd4hNHnB3drXsfle+wzSCA5Wo=
|
||||
github.com/tiendc/go-deepcopy v1.6.0/go.mod h1:toXoeQoUqXOOS/X4sKuiAoSk6elIdqc0pN7MTgOOo2I=
|
||||
github.com/urfave/cli/v2 v2.27.6 h1:VdRdS98FNhKZ8/Az8B7MTyGQmpIr36O1EHybx/LaZ4g=
|
||||
github.com/urfave/cli/v2 v2.27.6/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||
github.com/vektah/gqlparser/v2 v2.5.27 h1:RHPD3JOplpk5mP5JGX8RKZkt2/Vwj/PZv0HxTdwFp0s=
|
||||
github.com/vektah/gqlparser/v2 v2.5.27/go.mod h1:D1/VCZtV3LPnQrcPBeR/q5jkSQIPti0uYCP/RI0gIeo=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
|
||||
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
|
||||
github.com/xuri/excelize/v2 v2.9.1 h1:VdSGk+rraGmgLHGFaGG9/9IWu1nj4ufjJ7uwMDtj8Qw=
|
||||
github.com/xuri/excelize/v2 v2.9.1/go.mod h1:x7L6pKz2dvo9ejrRuD8Lnl98z4JLt0TGAwjhW+EiP8s=
|
||||
github.com/xuri/nfp v0.0.1 h1:MDamSGatIvp8uOmDP8FnmjuQpu90NzdJxo7242ANR9Q=
|
||||
github.com/xuri/nfp v0.0.1/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
|
||||
go.gearno.de/crypto/uuid v0.1.0 h1:94BYg7GYItJ6yYZ1GJayb3VYhI9/FjxuR1nFaduR4hE=
|
||||
go.gearno.de/crypto/uuid v0.1.0/go.mod h1:fnIIvKO9QnsyLO3ZJLJT3r8KZv/p0FOeT5eZKilYWXg=
|
||||
go.gearno.de/kit v0.0.0-20250623163305-45b4f6905899 h1:g44W/Fhm5bW7fhMqr874dJKOuHHsRhv6Tr3h6yHLcqY=
|
||||
@@ -186,6 +199,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
|
||||
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
|
||||
golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
|
||||
golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
|
||||
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"archive/tar"
|
||||
@@ -31,9 +32,16 @@ import (
|
||||
"github.com/getprobo/probo/pkg/gid"
|
||||
"github.com/getprobo/probo/pkg/page"
|
||||
"github.com/getprobo/probo/pkg/slug"
|
||||
"github.com/xuri/excelize/v2"
|
||||
"go.gearno.de/kit/pg"
|
||||
)
|
||||
|
||||
const (
|
||||
maxControlsLimit = 10000
|
||||
maxItemsLimit = 1000
|
||||
presignExpiry = 15 * time.Minute
|
||||
)
|
||||
|
||||
type (
|
||||
FrameworkService struct {
|
||||
svc *TenantService
|
||||
@@ -62,6 +70,18 @@ type (
|
||||
} `json:"controls"`
|
||||
}
|
||||
}
|
||||
|
||||
soaRowData struct {
|
||||
control *coredata.Control
|
||||
applicability string
|
||||
justificationExclusion string
|
||||
regulatory string
|
||||
contractual string
|
||||
bestPractice string
|
||||
riskAssessment string
|
||||
securityMeasures string
|
||||
isApplicable bool
|
||||
}
|
||||
)
|
||||
|
||||
func (s FrameworkService) Create(
|
||||
@@ -80,9 +100,7 @@ func (s FrameworkService) Create(
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
err := s.svc.pg.WithTx(ctx, func(conn pg.Conn) error {
|
||||
if err := organization.LoadByID(ctx, conn, s.svc.scope, req.OrganizationID); err != nil {
|
||||
return fmt.Errorf("cannot load organization: %w", err)
|
||||
}
|
||||
@@ -94,8 +112,7 @@ func (s FrameworkService) Create(
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -110,18 +127,14 @@ func (s FrameworkService) CountForOrganizationID(
|
||||
) (int, error) {
|
||||
var count int
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) (err error) {
|
||||
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) (err error) {
|
||||
frameworks := &coredata.Frameworks{}
|
||||
count, err = frameworks.CountByOrganizationID(ctx, conn, s.svc.scope, organizationID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot count frameworks: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("cannot count frameworks: %w", err)
|
||||
@@ -138,9 +151,7 @@ func (s FrameworkService) ListForOrganizationID(
|
||||
var frameworks coredata.Frameworks
|
||||
organization := &coredata.Organization{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||
if err := organization.LoadByID(ctx, conn, s.svc.scope, organizationID); err != nil {
|
||||
return fmt.Errorf("cannot load organization: %w", err)
|
||||
}
|
||||
@@ -157,8 +168,7 @@ func (s FrameworkService) ListForOrganizationID(
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -173,12 +183,9 @@ func (s FrameworkService) Get(
|
||||
) (*coredata.Framework, error) {
|
||||
framework := &coredata.Framework{}
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||
return framework.LoadByID(ctx, conn, s.svc.scope, frameworkID)
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -193,9 +200,7 @@ func (s FrameworkService) Update(
|
||||
) (*coredata.Framework, error) {
|
||||
framework := &coredata.Framework{ID: req.ID}
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
err := s.svc.pg.WithTx(ctx, func(conn pg.Conn) error {
|
||||
if err := framework.LoadByID(ctx, conn, s.svc.scope, req.ID); err != nil {
|
||||
return fmt.Errorf("cannot load framework: %w", err)
|
||||
}
|
||||
@@ -209,8 +214,7 @@ func (s FrameworkService) Update(
|
||||
}
|
||||
|
||||
return framework.Update(ctx, conn, s.svc.scope)
|
||||
},
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -224,12 +228,9 @@ func (s FrameworkService) Delete(
|
||||
) error {
|
||||
framework := &coredata.Framework{}
|
||||
|
||||
return s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||
return framework.Delete(ctx, conn, s.svc.scope, frameworkID)
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
func (s FrameworkService) Import(
|
||||
@@ -241,9 +242,7 @@ func (s FrameworkService) Import(
|
||||
frameworkID := gid.New(organizationID.TenantID(), coredata.FrameworkEntityType)
|
||||
now := time.Now()
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(tx pg.Conn) error {
|
||||
err := s.svc.pg.WithTx(ctx, func(tx pg.Conn) error {
|
||||
organization := &coredata.Organization{}
|
||||
if err := organization.LoadByID(ctx, tx, s.svc.scope, organizationID); err != nil {
|
||||
return fmt.Errorf("cannot load organization: %w", err)
|
||||
@@ -283,8 +282,7 @@ func (s FrameworkService) Import(
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -300,9 +298,7 @@ func (s FrameworkService) ExportAudit(
|
||||
var archivePath string
|
||||
var objectKey string
|
||||
|
||||
err := s.svc.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||
framework := &coredata.Framework{}
|
||||
if err := framework.LoadByID(ctx, conn, s.svc.scope, frameworkID); err != nil {
|
||||
return fmt.Errorf("cannot load framework: %w", err)
|
||||
@@ -315,9 +311,8 @@ func (s FrameworkService) ExportAudit(
|
||||
return fmt.Errorf("cannot create export directory: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("Exporting framework", framework.Name, "to", exportDir)
|
||||
|
||||
cursor := page.NewCursor(
|
||||
controls := coredata.Controls{}
|
||||
controlsCursor := page.NewCursor(
|
||||
0,
|
||||
nil,
|
||||
page.Head,
|
||||
@@ -327,17 +322,88 @@ func (s FrameworkService) ExportAudit(
|
||||
},
|
||||
)
|
||||
|
||||
controls := coredata.Controls{}
|
||||
if err := controls.LoadByFrameworkID(ctx, conn, s.svc.scope, frameworkID, cursor, coredata.NewControlFilter(nil)); err != nil {
|
||||
if err := controls.LoadByFrameworkID(ctx, conn, s.svc.scope, frameworkID, controlsCursor, coredata.NewControlFilter(nil)); err != nil {
|
||||
return fmt.Errorf("cannot load controls: %w", err)
|
||||
}
|
||||
|
||||
for _, control := range controls {
|
||||
if err := s.exportControlData(ctx, conn, control, exportDir); err != nil {
|
||||
return fmt.Errorf("cannot export control data: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
archivePath = exportDir + ".tar.gz"
|
||||
if err := createTarGzArchive(exportDir, archivePath); err != nil {
|
||||
return fmt.Errorf("cannot create archive: %w", err)
|
||||
}
|
||||
defer os.Remove(archivePath)
|
||||
|
||||
file, err := os.Open(archivePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot open archive file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
objectKey = fmt.Sprintf("exports/%s/%s.tar.gz", frameworkID, now.Format("2006-01-02-15-04-05"))
|
||||
_, err = s.svc.s3.PutObject(ctx, &s3.PutObjectInput{
|
||||
Bucket: aws.String(s.svc.bucket),
|
||||
Key: aws.String(objectKey),
|
||||
Body: file,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot upload archive to S3: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
presignClient := s3.NewPresignClient(s.svc.s3)
|
||||
|
||||
presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: aws.String(s.svc.bucket),
|
||||
Key: aws.String(objectKey),
|
||||
}, func(opts *s3.PresignOptions) {
|
||||
opts.Expires = presignExpiry
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot generate presigned URL: %w", err)
|
||||
}
|
||||
|
||||
return presignedReq.URL, nil
|
||||
}
|
||||
|
||||
func (s FrameworkService) exportControlData(ctx context.Context, conn pg.Conn, control *coredata.Control, exportDir string) error {
|
||||
controlDir := filepath.Join(exportDir, filepath.Base(control.SectionTitle))
|
||||
if err := os.MkdirAll(controlDir, 0755); err != nil {
|
||||
return fmt.Errorf("cannot create control directory: %w", err)
|
||||
}
|
||||
|
||||
measures, err := s.loadMeasuresForControl(ctx, conn, control.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load measures: %w", err)
|
||||
}
|
||||
|
||||
documents, err := s.loadDocumentsForControl(ctx, conn, control.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot load documents: %w", err)
|
||||
}
|
||||
|
||||
if err := s.exportDocuments(ctx, conn, documents, controlDir); err != nil {
|
||||
return fmt.Errorf("cannot export documents: %w", err)
|
||||
}
|
||||
|
||||
if err := s.exportMeasures(ctx, conn, measures, controlDir); err != nil {
|
||||
return fmt.Errorf("cannot export measures: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s FrameworkService) loadMeasuresForControl(ctx context.Context, conn pg.Conn, controlID gid.GID) (coredata.Measures, error) {
|
||||
measures := coredata.Measures{}
|
||||
cursor := page.NewCursor(
|
||||
0,
|
||||
@@ -349,13 +415,13 @@ func (s FrameworkService) ExportAudit(
|
||||
},
|
||||
)
|
||||
|
||||
if err := measures.LoadByControlID(ctx, conn, s.svc.scope, control.ID, cursor, coredata.NewMeasureFilter(nil)); err != nil {
|
||||
return fmt.Errorf("cannot load measures: %w", err)
|
||||
}
|
||||
err := measures.LoadByControlID(ctx, conn, s.svc.scope, controlID, cursor, coredata.NewMeasureFilter(nil))
|
||||
return measures, err
|
||||
}
|
||||
|
||||
func (s FrameworkService) loadDocumentsForControl(ctx context.Context, conn pg.Conn, controlID gid.GID) (coredata.Documents, error) {
|
||||
documents := coredata.Documents{}
|
||||
|
||||
cursor2 := page.NewCursor(
|
||||
cursor := page.NewCursor(
|
||||
0,
|
||||
nil,
|
||||
page.Head,
|
||||
@@ -365,10 +431,11 @@ func (s FrameworkService) ExportAudit(
|
||||
},
|
||||
)
|
||||
|
||||
if err := documents.LoadByControlID(ctx, conn, s.svc.scope, control.ID, cursor2, coredata.NewDocumentFilter(nil)); err != nil {
|
||||
return fmt.Errorf("cannot load documents: %w", err)
|
||||
}
|
||||
err := documents.LoadByControlID(ctx, conn, s.svc.scope, controlID, cursor, coredata.NewDocumentFilter(nil))
|
||||
return documents, err
|
||||
}
|
||||
|
||||
func (s FrameworkService) exportDocuments(ctx context.Context, conn pg.Conn, documents coredata.Documents, controlDir string) error {
|
||||
for _, document := range documents {
|
||||
documentDir := filepath.Join(controlDir, filepath.Base(document.Title))
|
||||
if err := os.MkdirAll(documentDir, 0755); err != nil {
|
||||
@@ -385,7 +452,10 @@ func (s FrameworkService) ExportAudit(
|
||||
return fmt.Errorf("cannot write document file: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s FrameworkService) exportMeasures(ctx context.Context, conn pg.Conn, measures coredata.Measures, controlDir string) error {
|
||||
for _, measure := range measures {
|
||||
measureDir := filepath.Join(controlDir, filepath.Base(measure.Name))
|
||||
if err := os.MkdirAll(measureDir, 0755); err != nil {
|
||||
@@ -408,20 +478,28 @@ func (s FrameworkService) ExportAudit(
|
||||
}
|
||||
|
||||
for _, evidence := range evidences {
|
||||
if err := s.exportEvidence(ctx, evidence, measureDir); err != nil {
|
||||
return fmt.Errorf("cannot export evidence: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s FrameworkService) exportEvidence(ctx context.Context, evidence *coredata.Evidence, measureDir string) error {
|
||||
if evidence.Type != coredata.EvidenceTypeFile || evidence.ObjectKey == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
evidenceFile := filepath.Join(measureDir, filepath.Base(evidence.Filename))
|
||||
|
||||
if evidence.Type == coredata.EvidenceTypeFile && evidence.ObjectKey != "" {
|
||||
output, err := s.svc.s3.GetObject(
|
||||
ctx,
|
||||
&s3.GetObjectInput{
|
||||
output, err := s.svc.s3.GetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: aws.String(s.svc.bucket),
|
||||
Key: aws.String(evidence.ObjectKey),
|
||||
},
|
||||
)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot download evidence file: %w", err)
|
||||
}
|
||||
|
||||
defer output.Body.Close()
|
||||
|
||||
file, err := os.Create(evidenceFile)
|
||||
@@ -434,57 +512,8 @@ func (s FrameworkService) ExportAudit(
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot write evidence file: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
archivePath = exportDir + ".tar.gz"
|
||||
if err := createTarGzArchive(exportDir, archivePath); err != nil {
|
||||
return fmt.Errorf("cannot create archive: %w", err)
|
||||
}
|
||||
defer os.Remove(archivePath)
|
||||
|
||||
file, err := os.Open(archivePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot open archive file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
objectKey = fmt.Sprintf("exports/%s/%s.tar.gz", frameworkID, now.Format("2006-01-02-15-04-05"))
|
||||
_, err = s.svc.s3.PutObject(
|
||||
ctx,
|
||||
&s3.PutObjectInput{
|
||||
Bucket: aws.String(s.svc.bucket),
|
||||
Key: aws.String(objectKey),
|
||||
Body: file,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot upload archive to S3: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
presignClient := s3.NewPresignClient(s.svc.s3)
|
||||
|
||||
presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: aws.String(s.svc.bucket),
|
||||
Key: aws.String(objectKey),
|
||||
}, func(opts *s3.PresignOptions) {
|
||||
opts.Expires = 15 * time.Minute
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot generate presigned URL: %w", err)
|
||||
}
|
||||
|
||||
return presignedReq.URL, nil
|
||||
}
|
||||
|
||||
func createTarGzArchive(sourceDir, targetFile string) error {
|
||||
@@ -538,3 +567,459 @@ func createTarGzArchive(sourceDir, targetFile string) error {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s FrameworkService) StateOfApplicability(ctx context.Context, frameworkID gid.GID) (string, error) {
|
||||
framework := &coredata.Framework{}
|
||||
var presignedURL string
|
||||
|
||||
err := s.svc.pg.WithConn(ctx, func(conn pg.Conn) error {
|
||||
if err := framework.LoadByID(ctx, conn, s.svc.scope, frameworkID); err != nil {
|
||||
return fmt.Errorf("cannot load framework: %w", err)
|
||||
}
|
||||
|
||||
controls, err := s.loadAllControlsForFramework(ctx, conn, frameworkID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
soaData, err := s.buildSOAData(ctx, conn, controls)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
excelFilePath, err := s.createSOAExcelFile(framework, soaData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.Remove(excelFilePath) // Clean up local file after upload
|
||||
|
||||
// Upload to S3 and get presigned URL
|
||||
presignedURL, err = s.uploadSOAToS3(ctx, framework, excelFilePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot upload SOA to S3: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return presignedURL, nil
|
||||
}
|
||||
|
||||
func (s FrameworkService) loadAllControlsForFramework(ctx context.Context, conn pg.Conn, frameworkID gid.GID) (coredata.Controls, error) {
|
||||
controls := coredata.Controls{}
|
||||
controlsCursor := page.NewCursor(
|
||||
maxControlsLimit,
|
||||
nil,
|
||||
page.Head,
|
||||
page.OrderBy[coredata.ControlOrderField]{
|
||||
Field: coredata.ControlOrderFieldSectionTitle,
|
||||
Direction: page.OrderDirectionAsc,
|
||||
},
|
||||
)
|
||||
|
||||
if err := controls.LoadByFrameworkID(ctx, conn, s.svc.scope, frameworkID, controlsCursor, coredata.NewControlFilter(nil)); err != nil {
|
||||
return nil, fmt.Errorf("cannot load controls: %w", err)
|
||||
}
|
||||
|
||||
return controls, nil
|
||||
}
|
||||
|
||||
func (s FrameworkService) buildSOAData(ctx context.Context, conn pg.Conn, controls coredata.Controls) ([]soaRowData, error) {
|
||||
var soaData []soaRowData
|
||||
|
||||
for _, control := range controls {
|
||||
if control == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
rowData, err := s.buildSOARowData(ctx, conn, control)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot build SOA row data for control %s: %w", control.ID, err)
|
||||
}
|
||||
|
||||
soaData = append(soaData, rowData)
|
||||
}
|
||||
|
||||
return soaData, nil
|
||||
}
|
||||
|
||||
func (s FrameworkService) buildSOARowData(ctx context.Context, conn pg.Conn, control *coredata.Control) (soaRowData, error) {
|
||||
measures, err := s.loadMeasuresForControl(ctx, conn, control.ID)
|
||||
if err != nil {
|
||||
return soaRowData{}, fmt.Errorf("cannot load measures: %w", err)
|
||||
}
|
||||
|
||||
policies, err := s.loadDocumentsForControl(ctx, conn, control.ID)
|
||||
if err != nil {
|
||||
return soaRowData{}, fmt.Errorf("cannot load policies: %w", err)
|
||||
}
|
||||
|
||||
hasEvidence := false
|
||||
for _, measure := range measures {
|
||||
if measure == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
evidences := coredata.Evidences{}
|
||||
evidencesCursor := page.NewCursor(
|
||||
maxItemsLimit,
|
||||
nil,
|
||||
page.Head,
|
||||
page.OrderBy[coredata.EvidenceOrderField]{
|
||||
Field: coredata.EvidenceOrderFieldCreatedAt,
|
||||
Direction: page.OrderDirectionAsc,
|
||||
},
|
||||
)
|
||||
|
||||
if err := evidences.LoadByMeasureID(ctx, conn, s.svc.scope, measure.ID, evidencesCursor); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(evidences) > 0 {
|
||||
hasEvidence = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
rowData := soaRowData{
|
||||
control: control,
|
||||
}
|
||||
|
||||
if len(measures) > 0 || len(policies) > 0 {
|
||||
rowData.applicability = "Yes"
|
||||
rowData.isApplicable = true
|
||||
rowData.regulatory = "YES"
|
||||
rowData.bestPractice = "YES"
|
||||
|
||||
if hasEvidence {
|
||||
rowData.riskAssessment = "YES"
|
||||
}
|
||||
|
||||
var measuresList []string
|
||||
for _, measure := range measures {
|
||||
if measure != nil {
|
||||
measuresList = append(measuresList, "• "+measure.Name)
|
||||
}
|
||||
}
|
||||
for _, policy := range policies {
|
||||
if policy != nil {
|
||||
measuresList = append(measuresList, "• "+policy.Title)
|
||||
}
|
||||
}
|
||||
rowData.securityMeasures = strings.Join(measuresList, "\n")
|
||||
} else {
|
||||
rowData.applicability = "No"
|
||||
rowData.isApplicable = false
|
||||
rowData.justificationExclusion = "Not applicable to current business operations"
|
||||
}
|
||||
|
||||
return rowData, nil
|
||||
}
|
||||
|
||||
func (s FrameworkService) createSOAExcelFile(framework *coredata.Framework, soaData []soaRowData) (string, error) {
|
||||
f := excelize.NewFile()
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
fmt.Printf("Error closing Excel file: %v\n", err)
|
||||
}
|
||||
}()
|
||||
|
||||
sheetName := "State of Applicability"
|
||||
f.SetSheetName("Sheet1", sheetName)
|
||||
|
||||
styles, err := s.createExcelStyles(f)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot create Excel styles: %w", err)
|
||||
}
|
||||
|
||||
if err := s.setupExcelHeader(f, sheetName, styles); err != nil {
|
||||
return "", fmt.Errorf("cannot setup Excel header: %w", err)
|
||||
}
|
||||
|
||||
if err := s.populateExcelData(f, sheetName, soaData, styles); err != nil {
|
||||
return "", fmt.Errorf("cannot populate Excel data: %w", err)
|
||||
}
|
||||
|
||||
if err := s.applyExcelFormatting(f, sheetName, len(soaData)); err != nil {
|
||||
return "", fmt.Errorf("cannot apply Excel formatting: %w", err)
|
||||
}
|
||||
|
||||
fileName := s.generateSOAFileName(framework)
|
||||
excelFilePath := filepath.Join("./", fileName)
|
||||
|
||||
if err := f.SaveAs(excelFilePath); err != nil {
|
||||
return "", fmt.Errorf("cannot save Excel file: %w", err)
|
||||
}
|
||||
|
||||
return excelFilePath, nil
|
||||
}
|
||||
|
||||
func (s FrameworkService) createExcelStyles(f *excelize.File) (map[string]int, error) {
|
||||
styles := make(map[string]int)
|
||||
|
||||
headerStyle, err := f.NewStyle(&excelize.Style{
|
||||
Font: &excelize.Font{Bold: true, Size: 10, Color: "#000000"},
|
||||
Fill: excelize.Fill{Type: "pattern", Color: []string{"#D9D9D9"}, Pattern: 1},
|
||||
Border: []excelize.Border{
|
||||
{Type: "left", Color: "#000000", Style: 1},
|
||||
{Type: "top", Color: "#000000", Style: 1},
|
||||
{Type: "bottom", Color: "#000000", Style: 1},
|
||||
{Type: "right", Color: "#000000", Style: 1},
|
||||
},
|
||||
Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
styles["header"] = headerStyle
|
||||
|
||||
cellStyle, err := f.NewStyle(&excelize.Style{
|
||||
Border: []excelize.Border{
|
||||
{Type: "left", Color: "#000000", Style: 1},
|
||||
{Type: "top", Color: "#000000", Style: 1},
|
||||
{Type: "bottom", Color: "#000000", Style: 1},
|
||||
{Type: "right", Color: "#000000", Style: 1},
|
||||
},
|
||||
Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
styles["cell"] = cellStyle
|
||||
|
||||
textCellStyle, err := f.NewStyle(&excelize.Style{
|
||||
Border: []excelize.Border{
|
||||
{Type: "left", Color: "#000000", Style: 1},
|
||||
{Type: "top", Color: "#000000", Style: 1},
|
||||
{Type: "bottom", Color: "#000000", Style: 1},
|
||||
{Type: "right", Color: "#000000", Style: 1},
|
||||
},
|
||||
Alignment: &excelize.Alignment{Horizontal: "left", Vertical: "center", WrapText: true},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
styles["textCell"] = textCellStyle
|
||||
|
||||
greenApplicabilityStyle, err := f.NewStyle(&excelize.Style{
|
||||
Border: []excelize.Border{
|
||||
{Type: "left", Color: "#000000", Style: 1},
|
||||
{Type: "top", Color: "#000000", Style: 1},
|
||||
{Type: "bottom", Color: "#000000", Style: 1},
|
||||
{Type: "right", Color: "#000000", Style: 1},
|
||||
},
|
||||
Fill: excelize.Fill{Type: "pattern", Color: []string{"#90EE90"}, Pattern: 1},
|
||||
Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
styles["greenApplicability"] = greenApplicabilityStyle
|
||||
|
||||
redApplicabilityStyle, err := f.NewStyle(&excelize.Style{
|
||||
Border: []excelize.Border{
|
||||
{Type: "left", Color: "#000000", Style: 1},
|
||||
{Type: "top", Color: "#000000", Style: 1},
|
||||
{Type: "bottom", Color: "#000000", Style: 1},
|
||||
{Type: "right", Color: "#000000", Style: 1},
|
||||
},
|
||||
Fill: excelize.Fill{Type: "pattern", Color: []string{"#FFB6C1"}, Pattern: 1},
|
||||
Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
styles["redApplicability"] = redApplicabilityStyle
|
||||
|
||||
return styles, nil
|
||||
}
|
||||
|
||||
func (s FrameworkService) setupExcelHeader(f *excelize.File, sheetName string, styles map[string]int) error {
|
||||
// Version, Date, Comment, Author, Approver header
|
||||
f.SetCellValue(sheetName, "A2", "Version")
|
||||
f.SetCellValue(sheetName, "C2", "Date")
|
||||
f.SetCellValue(sheetName, "D2", "Comment")
|
||||
f.SetCellValue(sheetName, "E2", "Author")
|
||||
f.SetCellValue(sheetName, "F2", "Approver")
|
||||
|
||||
// Sample data for header
|
||||
f.SetCellValue(sheetName, "A3", "1.0")
|
||||
f.SetCellValue(sheetName, "C3", time.Now().Format("01/02/2006"))
|
||||
f.SetCellValue(sheetName, "D3", "Initial SoA")
|
||||
f.SetCellValue(sheetName, "E3", "System Admin")
|
||||
f.SetCellValue(sheetName, "F3", "Security Manager")
|
||||
|
||||
// Apply header styles
|
||||
for _, cell := range []string{"A2", "C2", "D2", "E2", "F2"} {
|
||||
f.SetCellStyle(sheetName, cell, cell, styles["header"])
|
||||
}
|
||||
for _, cell := range []string{"A3", "C3", "D3", "E3", "F3"} {
|
||||
f.SetCellStyle(sheetName, cell, cell, styles["cell"])
|
||||
}
|
||||
|
||||
// Main table headers (row 6-7)
|
||||
f.SetCellValue(sheetName, "A6", "Control")
|
||||
f.SetCellValue(sheetName, "B6", "Control name")
|
||||
f.SetCellValue(sheetName, "C6", "Applicability")
|
||||
f.SetCellValue(sheetName, "D6", "Justification for exclusion")
|
||||
f.SetCellValue(sheetName, "E6", "Justification for inclusion")
|
||||
f.SetCellValue(sheetName, "I6", "List of security measure or policy")
|
||||
|
||||
// Sub headers for "Justification for inclusion"
|
||||
f.SetCellValue(sheetName, "E7", "Regulatory")
|
||||
f.SetCellValue(sheetName, "F7", "Contractual")
|
||||
f.SetCellValue(sheetName, "G7", "Best practice")
|
||||
f.SetCellValue(sheetName, "H7", "Risk assessment")
|
||||
|
||||
// Merge cells for main headers
|
||||
f.MergeCell(sheetName, "A6", "A7")
|
||||
f.MergeCell(sheetName, "B6", "B7")
|
||||
f.MergeCell(sheetName, "C6", "C7")
|
||||
f.MergeCell(sheetName, "D6", "D7")
|
||||
f.MergeCell(sheetName, "E6", "H6")
|
||||
f.MergeCell(sheetName, "I6", "I7")
|
||||
|
||||
// Apply header styles
|
||||
headerCells := []string{"A6", "A7", "B6", "B7", "C6", "C7", "D6", "D7", "E6", "I6", "I7", "E6", "E7", "F6", "F7", "G6", "G7", "H6", "H7"}
|
||||
for _, cell := range headerCells {
|
||||
f.SetCellStyle(sheetName, cell, cell, styles["header"])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s FrameworkService) populateExcelData(f *excelize.File, sheetName string, soaData []soaRowData, styles map[string]int) error {
|
||||
currentRow := 8
|
||||
|
||||
for _, rowData := range soaData {
|
||||
control := rowData.control
|
||||
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("A%d", currentRow), control.SectionTitle)
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("B%d", currentRow), control.Name)
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("C%d", currentRow), rowData.applicability)
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("D%d", currentRow), rowData.justificationExclusion)
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("E%d", currentRow), rowData.regulatory)
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("F%d", currentRow), rowData.contractual)
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("G%d", currentRow), rowData.bestPractice)
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("H%d", currentRow), rowData.riskAssessment)
|
||||
f.SetCellValue(sheetName, fmt.Sprintf("I%d", currentRow), rowData.securityMeasures)
|
||||
|
||||
// Apply appropriate styles
|
||||
for col := 'A'; col <= 'I'; col++ {
|
||||
cellRef := fmt.Sprintf("%c%d", col, currentRow)
|
||||
if col == 'C' { // Applicability column
|
||||
if rowData.isApplicable {
|
||||
f.SetCellStyle(sheetName, cellRef, cellRef, styles["greenApplicability"])
|
||||
} else {
|
||||
f.SetCellStyle(sheetName, cellRef, cellRef, styles["redApplicability"])
|
||||
}
|
||||
} else if col >= 'E' && col <= 'H' { // Justification columns
|
||||
f.SetCellStyle(sheetName, cellRef, cellRef, styles["cell"])
|
||||
} else { // Other columns
|
||||
f.SetCellStyle(sheetName, cellRef, cellRef, styles["textCell"])
|
||||
}
|
||||
}
|
||||
|
||||
currentRow++
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s FrameworkService) applyExcelFormatting(f *excelize.File, sheetName string, dataRowCount int) error {
|
||||
if dataRowCount == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
lastRow := 8 + dataRowCount - 1
|
||||
|
||||
// Add data validation for Applicability column
|
||||
dvRange := fmt.Sprintf("C8:C%d", lastRow)
|
||||
dv := excelize.NewDataValidation(true)
|
||||
dv.Sqref = dvRange
|
||||
dv.SetDropList([]string{"Yes", "No"})
|
||||
dv.SetError(excelize.DataValidationErrorStyleStop, "Invalid Input", "Please select Yes or No from the dropdown list.")
|
||||
if err := f.AddDataValidation(sheetName, dv); err != nil {
|
||||
return fmt.Errorf("failed to add data validation: %w", err)
|
||||
}
|
||||
|
||||
// Add data validation for justification columns
|
||||
for _, col := range []string{"E", "F", "G", "H"} {
|
||||
dvRange := fmt.Sprintf("%s8:%s%d", col, col, lastRow)
|
||||
dv := excelize.NewDataValidation(true)
|
||||
dv.Sqref = dvRange
|
||||
dv.SetDropList([]string{"YES", "NO", ""})
|
||||
dv.SetError(excelize.DataValidationErrorStyleStop, "Invalid Input", "Please select YES, NO, or leave empty.")
|
||||
if err := f.AddDataValidation(sheetName, dv); err != nil {
|
||||
return fmt.Errorf("failed to add data validation for column %s: %w", col, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Add auto-filter
|
||||
filterRange := fmt.Sprintf("A6:I%d", lastRow)
|
||||
if err := f.AutoFilter(sheetName, filterRange, []excelize.AutoFilterOptions{}); err != nil {
|
||||
return fmt.Errorf("failed to add auto-filter: %w", err)
|
||||
}
|
||||
|
||||
// Set column widths
|
||||
f.SetColWidth(sheetName, "A", "A", 12)
|
||||
f.SetColWidth(sheetName, "B", "B", 35)
|
||||
f.SetColWidth(sheetName, "C", "C", 12)
|
||||
f.SetColWidth(sheetName, "D", "D", 25)
|
||||
f.SetColWidth(sheetName, "E", "E", 12)
|
||||
f.SetColWidth(sheetName, "F", "F", 12)
|
||||
f.SetColWidth(sheetName, "G", "G", 12)
|
||||
f.SetColWidth(sheetName, "H", "H", 12)
|
||||
f.SetColWidth(sheetName, "I", "I", 40)
|
||||
|
||||
// Set row heights for better visibility
|
||||
f.SetRowHeight(sheetName, 6, 30)
|
||||
f.SetRowHeight(sheetName, 7, 30)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s FrameworkService) generateSOAFileName(framework *coredata.Framework) string {
|
||||
now := time.Now()
|
||||
return fmt.Sprintf("SOA_%s_%s.xlsx",
|
||||
strings.ReplaceAll(framework.Name, " ", "_"),
|
||||
now.Format("20060102_150405"))
|
||||
}
|
||||
|
||||
func (s FrameworkService) uploadSOAToS3(ctx context.Context, framework *coredata.Framework, excelFilePath string) (string, error) {
|
||||
file, err := os.Open(excelFilePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot open Excel file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
objectKey := fmt.Sprintf("soa/%s/%s", framework.ID, s.generateSOAFileName(framework))
|
||||
|
||||
// Upload file to S3
|
||||
_, err = s.svc.s3.PutObject(ctx, &s3.PutObjectInput{
|
||||
Bucket: aws.String(s.svc.bucket),
|
||||
Key: aws.String(objectKey),
|
||||
Body: file,
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot upload SOA file to S3: %w", err)
|
||||
}
|
||||
|
||||
// Generate presigned URL
|
||||
presignClient := s3.NewPresignClient(s.svc.s3)
|
||||
presignedReq, err := presignClient.PresignGetObject(ctx, &s3.GetObjectInput{
|
||||
Bucket: aws.String(s.svc.bucket),
|
||||
Key: aws.String(objectKey),
|
||||
}, func(opts *s3.PresignOptions) {
|
||||
opts.Expires = presignExpiry
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot generate presigned URL: %w", err)
|
||||
}
|
||||
|
||||
return presignedReq.URL, nil
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -234,7 +235,7 @@ func graphqlHandler(logger *log.Logger, proboSvc *probo.Service, usrmgrSvc *usrm
|
||||
srv.Use(tracingExtension{})
|
||||
srv.SetRecoverFunc(func(ctx context.Context, err any) error {
|
||||
logger := httpserver.LoggerFromContext(ctx)
|
||||
logger.Error("resolver panic", log.Any("error", err))
|
||||
logger.Error("resolver panic", log.Any("error", err), log.Any("stack", string(debug.Stack())))
|
||||
|
||||
return errors.New("internal server error")
|
||||
})
|
||||
|
||||
@@ -1289,6 +1289,9 @@ type Mutation {
|
||||
updateFramework(input: UpdateFrameworkInput!): UpdateFrameworkPayload!
|
||||
importFramework(input: ImportFrameworkInput!): ImportFrameworkPayload!
|
||||
deleteFramework(input: DeleteFrameworkInput!): DeleteFrameworkPayload!
|
||||
generateFrameworkStateOfApplicability(
|
||||
input: GenerateFrameworkStateOfApplicabilityInput!
|
||||
): GenerateFrameworkStateOfApplicabilityPayload!
|
||||
|
||||
# Control mutations
|
||||
createControl(input: CreateControlInput!): CreateControlPayload!
|
||||
@@ -1398,6 +1401,14 @@ type Mutation {
|
||||
}
|
||||
|
||||
# Input Types
|
||||
input GenerateFrameworkStateOfApplicabilityInput {
|
||||
frameworkId: ID!
|
||||
}
|
||||
|
||||
type GenerateFrameworkStateOfApplicabilityPayload {
|
||||
downloadUrl: String!
|
||||
}
|
||||
|
||||
input CreateOrganizationInput {
|
||||
name: String!
|
||||
}
|
||||
|
||||
@@ -456,6 +456,10 @@ type ComplexityRoot struct {
|
||||
Changelog func(childComplexity int) int
|
||||
}
|
||||
|
||||
GenerateFrameworkStateOfApplicabilityPayload struct {
|
||||
DownloadURL func(childComplexity int) int
|
||||
}
|
||||
|
||||
ImportFrameworkPayload struct {
|
||||
FrameworkEdge func(childComplexity int) int
|
||||
}
|
||||
@@ -533,6 +537,7 @@ type ComplexityRoot struct {
|
||||
ExportAudit func(childComplexity int, input types.ExportAuditInput) int
|
||||
FulfillEvidence func(childComplexity int, input types.FulfillEvidenceInput) int
|
||||
GenerateDocumentChangelog func(childComplexity int, input types.GenerateDocumentChangelogInput) int
|
||||
GenerateFrameworkStateOfApplicability func(childComplexity int, input types.GenerateFrameworkStateOfApplicabilityInput) int
|
||||
ImportFramework func(childComplexity int, input types.ImportFrameworkInput) int
|
||||
ImportMeasure func(childComplexity int, input types.ImportMeasureInput) int
|
||||
InviteUser func(childComplexity int, input types.InviteUserInput) int
|
||||
@@ -974,6 +979,7 @@ type MutationResolver interface {
|
||||
UpdateFramework(ctx context.Context, input types.UpdateFrameworkInput) (*types.UpdateFrameworkPayload, error)
|
||||
ImportFramework(ctx context.Context, input types.ImportFrameworkInput) (*types.ImportFrameworkPayload, error)
|
||||
DeleteFramework(ctx context.Context, input types.DeleteFrameworkInput) (*types.DeleteFrameworkPayload, error)
|
||||
GenerateFrameworkStateOfApplicability(ctx context.Context, input types.GenerateFrameworkStateOfApplicabilityInput) (*types.GenerateFrameworkStateOfApplicabilityPayload, error)
|
||||
CreateControl(ctx context.Context, input types.CreateControlInput) (*types.CreateControlPayload, error)
|
||||
UpdateControl(ctx context.Context, input types.UpdateControlInput) (*types.UpdateControlPayload, error)
|
||||
DeleteControl(ctx context.Context, input types.DeleteControlInput) (*types.DeleteControlPayload, error)
|
||||
@@ -2389,6 +2395,13 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
|
||||
return e.complexity.GenerateDocumentChangelogPayload.Changelog(childComplexity), true
|
||||
|
||||
case "GenerateFrameworkStateOfApplicabilityPayload.downloadUrl":
|
||||
if e.complexity.GenerateFrameworkStateOfApplicabilityPayload.DownloadURL == nil {
|
||||
break
|
||||
}
|
||||
|
||||
return e.complexity.GenerateFrameworkStateOfApplicabilityPayload.DownloadURL(childComplexity), true
|
||||
|
||||
case "ImportFrameworkPayload.frameworkEdge":
|
||||
if e.complexity.ImportFrameworkPayload.FrameworkEdge == nil {
|
||||
break
|
||||
@@ -3010,6 +3023,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
|
||||
|
||||
return e.complexity.Mutation.GenerateDocumentChangelog(childComplexity, args["input"].(types.GenerateDocumentChangelogInput)), true
|
||||
|
||||
case "Mutation.generateFrameworkStateOfApplicability":
|
||||
if e.complexity.Mutation.GenerateFrameworkStateOfApplicability == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Mutation_generateFrameworkStateOfApplicability_args(ctx, rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.GenerateFrameworkStateOfApplicability(childComplexity, args["input"].(types.GenerateFrameworkStateOfApplicabilityInput)), true
|
||||
|
||||
case "Mutation.importFramework":
|
||||
if e.complexity.Mutation.ImportFramework == nil {
|
||||
break
|
||||
@@ -4695,6 +4720,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
|
||||
ec.unmarshalInputFrameworkOrder,
|
||||
ec.unmarshalInputFulfillEvidenceInput,
|
||||
ec.unmarshalInputGenerateDocumentChangelogInput,
|
||||
ec.unmarshalInputGenerateFrameworkStateOfApplicabilityInput,
|
||||
ec.unmarshalInputImportFrameworkInput,
|
||||
ec.unmarshalInputImportMeasureInput,
|
||||
ec.unmarshalInputInviteUserInput,
|
||||
@@ -6118,6 +6144,9 @@ type Mutation {
|
||||
updateFramework(input: UpdateFrameworkInput!): UpdateFrameworkPayload!
|
||||
importFramework(input: ImportFrameworkInput!): ImportFrameworkPayload!
|
||||
deleteFramework(input: DeleteFrameworkInput!): DeleteFrameworkPayload!
|
||||
generateFrameworkStateOfApplicability(
|
||||
input: GenerateFrameworkStateOfApplicabilityInput!
|
||||
): GenerateFrameworkStateOfApplicabilityPayload!
|
||||
|
||||
# Control mutations
|
||||
createControl(input: CreateControlInput!): CreateControlPayload!
|
||||
@@ -6227,6 +6256,14 @@ type Mutation {
|
||||
}
|
||||
|
||||
# Input Types
|
||||
input GenerateFrameworkStateOfApplicabilityInput {
|
||||
frameworkId: ID!
|
||||
}
|
||||
|
||||
type GenerateFrameworkStateOfApplicabilityPayload {
|
||||
downloadUrl: String!
|
||||
}
|
||||
|
||||
input CreateOrganizationInput {
|
||||
name: String!
|
||||
}
|
||||
@@ -9273,6 +9310,29 @@ func (ec *executionContext) field_Mutation_generateDocumentChangelog_argsInput(
|
||||
return zeroVal, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_generateFrameworkStateOfApplicability_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||
var err error
|
||||
args := map[string]any{}
|
||||
arg0, err := ec.field_Mutation_generateFrameworkStateOfApplicability_argsInput(ctx, rawArgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
args["input"] = arg0
|
||||
return args, nil
|
||||
}
|
||||
func (ec *executionContext) field_Mutation_generateFrameworkStateOfApplicability_argsInput(
|
||||
ctx context.Context,
|
||||
rawArgs map[string]any,
|
||||
) (types.GenerateFrameworkStateOfApplicabilityInput, error) {
|
||||
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input"))
|
||||
if tmp, ok := rawArgs["input"]; ok {
|
||||
return ec.unmarshalNGenerateFrameworkStateOfApplicabilityInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐGenerateFrameworkStateOfApplicabilityInput(ctx, tmp)
|
||||
}
|
||||
|
||||
var zeroVal types.GenerateFrameworkStateOfApplicabilityInput
|
||||
return zeroVal, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_importFramework_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
|
||||
var err error
|
||||
args := map[string]any{}
|
||||
@@ -20810,6 +20870,50 @@ func (ec *executionContext) fieldContext_GenerateDocumentChangelogPayload_change
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _GenerateFrameworkStateOfApplicabilityPayload_downloadUrl(ctx context.Context, field graphql.CollectedField, obj *types.GenerateFrameworkStateOfApplicabilityPayload) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_GenerateFrameworkStateOfApplicabilityPayload_downloadUrl(ctx, field)
|
||||
if err != nil {
|
||||
return graphql.Null
|
||||
}
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return obj.DownloadURL, 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.(string)
|
||||
fc.Result = res
|
||||
return ec.marshalNString2string(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_GenerateFrameworkStateOfApplicabilityPayload_downloadUrl(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
fc = &graphql.FieldContext{
|
||||
Object: "GenerateFrameworkStateOfApplicabilityPayload",
|
||||
Field: field,
|
||||
IsMethod: false,
|
||||
IsResolver: false,
|
||||
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
|
||||
return nil, errors.New("field of type String does not have child fields")
|
||||
},
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _ImportFrameworkPayload_frameworkEdge(ctx context.Context, field graphql.CollectedField, obj *types.ImportFrameworkPayload) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_ImportFrameworkPayload_frameworkEdge(ctx, field)
|
||||
if err != nil {
|
||||
@@ -22659,6 +22763,65 @@ func (ec *executionContext) fieldContext_Mutation_deleteFramework(ctx context.Co
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_generateFrameworkStateOfApplicability(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Mutation_generateFrameworkStateOfApplicability(ctx, field)
|
||||
if err != nil {
|
||||
return graphql.Null
|
||||
}
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
ec.Error(ctx, ec.Recover(ctx, r))
|
||||
ret = graphql.Null
|
||||
}
|
||||
}()
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Mutation().GenerateFrameworkStateOfApplicability(rctx, fc.Args["input"].(types.GenerateFrameworkStateOfApplicabilityInput))
|
||||
})
|
||||
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.GenerateFrameworkStateOfApplicabilityPayload)
|
||||
fc.Result = res
|
||||
return ec.marshalNGenerateFrameworkStateOfApplicabilityPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐGenerateFrameworkStateOfApplicabilityPayload(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_Mutation_generateFrameworkStateOfApplicability(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 "downloadUrl":
|
||||
return ec.fieldContext_GenerateFrameworkStateOfApplicabilityPayload_downloadUrl(ctx, field)
|
||||
}
|
||||
return nil, fmt.Errorf("no field named %q was found under type GenerateFrameworkStateOfApplicabilityPayload", field.Name)
|
||||
},
|
||||
}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = ec.Recover(ctx, r)
|
||||
ec.Error(ctx, err)
|
||||
}
|
||||
}()
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
if fc.Args, err = ec.field_Mutation_generateFrameworkStateOfApplicability_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
|
||||
ec.Error(ctx, err)
|
||||
return fc, err
|
||||
}
|
||||
return fc, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_createControl(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
fc, err := ec.fieldContext_Mutation_createControl(ctx, field)
|
||||
if err != nil {
|
||||
@@ -38883,6 +39046,33 @@ func (ec *executionContext) unmarshalInputGenerateDocumentChangelogInput(ctx con
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputGenerateFrameworkStateOfApplicabilityInput(ctx context.Context, obj any) (types.GenerateFrameworkStateOfApplicabilityInput, error) {
|
||||
var it types.GenerateFrameworkStateOfApplicabilityInput
|
||||
asMap := map[string]any{}
|
||||
for k, v := range obj.(map[string]any) {
|
||||
asMap[k] = v
|
||||
}
|
||||
|
||||
fieldsInOrder := [...]string{"frameworkId"}
|
||||
for _, k := range fieldsInOrder {
|
||||
v, ok := asMap[k]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
switch k {
|
||||
case "frameworkId":
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("frameworkId"))
|
||||
data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
it.FrameworkID = data
|
||||
}
|
||||
}
|
||||
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputImportFrameworkInput(ctx context.Context, obj any) (types.ImportFrameworkInput, error) {
|
||||
var it types.ImportFrameworkInput
|
||||
asMap := map[string]any{}
|
||||
@@ -44756,6 +44946,45 @@ func (ec *executionContext) _GenerateDocumentChangelogPayload(ctx context.Contex
|
||||
return out
|
||||
}
|
||||
|
||||
var generateFrameworkStateOfApplicabilityPayloadImplementors = []string{"GenerateFrameworkStateOfApplicabilityPayload"}
|
||||
|
||||
func (ec *executionContext) _GenerateFrameworkStateOfApplicabilityPayload(ctx context.Context, sel ast.SelectionSet, obj *types.GenerateFrameworkStateOfApplicabilityPayload) graphql.Marshaler {
|
||||
fields := graphql.CollectFields(ec.OperationContext, sel, generateFrameworkStateOfApplicabilityPayloadImplementors)
|
||||
|
||||
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("GenerateFrameworkStateOfApplicabilityPayload")
|
||||
case "downloadUrl":
|
||||
out.Values[i] = ec._GenerateFrameworkStateOfApplicabilityPayload_downloadUrl(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 importFrameworkPayloadImplementors = []string{"ImportFrameworkPayload"}
|
||||
|
||||
func (ec *executionContext) _ImportFrameworkPayload(ctx context.Context, sel ast.SelectionSet, obj *types.ImportFrameworkPayload) graphql.Marshaler {
|
||||
@@ -45334,6 +45563,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "generateFrameworkStateOfApplicability":
|
||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||
return ec._Mutation_generateFrameworkStateOfApplicability(ctx, field)
|
||||
})
|
||||
if out.Values[i] == graphql.Null {
|
||||
out.Invalids++
|
||||
}
|
||||
case "createControl":
|
||||
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
|
||||
return ec._Mutation_createControl(ctx, field)
|
||||
@@ -51728,6 +51964,25 @@ func (ec *executionContext) marshalNGenerateDocumentChangelogPayload2ᚖgithub
|
||||
return ec._GenerateDocumentChangelogPayload(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNGenerateFrameworkStateOfApplicabilityInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐGenerateFrameworkStateOfApplicabilityInput(ctx context.Context, v any) (types.GenerateFrameworkStateOfApplicabilityInput, error) {
|
||||
res, err := ec.unmarshalInputGenerateFrameworkStateOfApplicabilityInput(ctx, v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNGenerateFrameworkStateOfApplicabilityPayload2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐGenerateFrameworkStateOfApplicabilityPayload(ctx context.Context, sel ast.SelectionSet, v types.GenerateFrameworkStateOfApplicabilityPayload) graphql.Marshaler {
|
||||
return ec._GenerateFrameworkStateOfApplicabilityPayload(ctx, sel, &v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNGenerateFrameworkStateOfApplicabilityPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐGenerateFrameworkStateOfApplicabilityPayload(ctx context.Context, sel ast.SelectionSet, v *types.GenerateFrameworkStateOfApplicabilityPayload) 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._GenerateFrameworkStateOfApplicabilityPayload(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx context.Context, v any) (gid.GID, error) {
|
||||
res, err := types.UnmarshalGIDScalar(v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
|
||||
@@ -654,6 +654,14 @@ type GenerateDocumentChangelogPayload struct {
|
||||
Changelog string `json:"changelog"`
|
||||
}
|
||||
|
||||
type GenerateFrameworkStateOfApplicabilityInput struct {
|
||||
FrameworkID gid.GID `json:"frameworkId"`
|
||||
}
|
||||
|
||||
type GenerateFrameworkStateOfApplicabilityPayload struct {
|
||||
DownloadURL string `json:"downloadUrl"`
|
||||
}
|
||||
|
||||
type ImportFrameworkInput struct {
|
||||
OrganizationID gid.GID `json:"organizationId"`
|
||||
File graphql.Upload `json:"file"`
|
||||
|
||||
@@ -1178,6 +1178,20 @@ func (r *mutationResolver) DeleteFramework(ctx context.Context, input types.Dele
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GenerateFrameworkStateOfApplicability is the resolver for the generateFrameworkStateOfApplicability field.
|
||||
func (r *mutationResolver) GenerateFrameworkStateOfApplicability(ctx context.Context, input types.GenerateFrameworkStateOfApplicabilityInput) (*types.GenerateFrameworkStateOfApplicabilityPayload, error) {
|
||||
prb := r.ProboService(ctx, input.FrameworkID.TenantID())
|
||||
|
||||
soa, err := prb.Frameworks.StateOfApplicability(ctx, input.FrameworkID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot generate framework SOA: %w", err)
|
||||
}
|
||||
|
||||
return &types.GenerateFrameworkStateOfApplicabilityPayload{
|
||||
DownloadURL: soa,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateControl is the resolver for the createControl field.
|
||||
func (r *mutationResolver) CreateControl(ctx context.Context, input types.CreateControlInput) (*types.CreateControlPayload, error) {
|
||||
prb := r.ProboService(ctx, input.FrameworkID.TenantID())
|
||||
|
||||
Reference in New Issue
Block a user