From 55d488edac5ac0afcb0db73e9e7f59b889b3e902 Mon Sep 17 00:00:00 2001 From: Sacha Al Himdani Date: Thu, 14 Aug 2025 13:42:47 +0200 Subject: [PATCH] Add contols audits Signed-off-by: Sacha Al Himdani --- .../components/audits/LinkedAuditsCard.tsx | 201 +++ .../components/audits/LinkedAuditsDialog.tsx | 195 +++ .../LinkedAuditsCardFragment.graphql.ts | 104 ++ .../LinkedAuditsDialogFragment.graphql.ts | 257 ++++ .../LinkedAuditsDialogQuery.graphql.ts | 273 ++++ ...inkedAuditsDialogQuery_fragment.graphql.ts | 346 +++++ .../console/src/hooks/graph/FrameworkGraph.ts | 10 + .../FrameworkGraphControlNodeQuery.graphql.ts | 178 ++- .../organizations/audits/AuditDetailsPage.tsx | 6 +- .../frameworks/FrameworkControlPage.tsx | 41 +- ...kControlPageAttachAuditMutation.graphql.ts | 237 ++++ ...kControlPageDetachAuditMutation.graphql.ts | 133 ++ pkg/coredata/audit.go | 106 ++ pkg/coredata/control.go | 111 ++ pkg/coredata/control_audit.go | 168 +++ pkg/coredata/migrations/20250814T123441Z.sql | 7 + pkg/probo/audit_service.go | 31 + pkg/probo/control_service.go | 105 ++ pkg/server/api/console/v1/schema.graphql | 44 + pkg/server/api/console/v1/schema/schema.go | 1123 +++++++++++++++++ pkg/server/api/console/v1/types/types.go | 22 + pkg/server/api/console/v1/v1_resolver.go | 85 ++ 22 files changed, 3762 insertions(+), 21 deletions(-) create mode 100644 apps/console/src/components/audits/LinkedAuditsCard.tsx create mode 100644 apps/console/src/components/audits/LinkedAuditsDialog.tsx create mode 100644 apps/console/src/components/audits/__generated__/LinkedAuditsCardFragment.graphql.ts create mode 100644 apps/console/src/components/audits/__generated__/LinkedAuditsDialogFragment.graphql.ts create mode 100644 apps/console/src/components/audits/__generated__/LinkedAuditsDialogQuery.graphql.ts create mode 100644 apps/console/src/components/audits/__generated__/LinkedAuditsDialogQuery_fragment.graphql.ts create mode 100644 apps/console/src/pages/organizations/frameworks/__generated__/FrameworkControlPageAttachAuditMutation.graphql.ts create mode 100644 apps/console/src/pages/organizations/frameworks/__generated__/FrameworkControlPageDetachAuditMutation.graphql.ts create mode 100644 pkg/coredata/control_audit.go create mode 100644 pkg/coredata/migrations/20250814T123441Z.sql diff --git a/apps/console/src/components/audits/LinkedAuditsCard.tsx b/apps/console/src/components/audits/LinkedAuditsCard.tsx new file mode 100644 index 000000000..8818cea8c --- /dev/null +++ b/apps/console/src/components/audits/LinkedAuditsCard.tsx @@ -0,0 +1,201 @@ +import { graphql } from "relay-runtime"; +import { + Card, + IconPlusLarge, + Button, + Tr, + Td, + Table, + Thead, + Tbody, + Th, + IconChevronDown, + IconTrashCan, + Badge, + TrButton, +} from "@probo/ui"; +import { useTranslate } from "@probo/i18n"; +import type { LinkedAuditsCardFragment$key } from "./__generated__/LinkedAuditsCardFragment.graphql"; +import { useFragment } from "react-relay"; +import { useMemo, useState } from "react"; +import { sprintf, getAuditStateVariant } from "@probo/helpers"; +import { useOrganizationId } from "/hooks/useOrganizationId"; +import { LinkedAuditsDialog } from "./LinkedAuditsDialog"; +import clsx from "clsx"; + +const linkedAuditFragment = graphql` + fragment LinkedAuditsCardFragment on Audit { + id + name + createdAt + state + validFrom + validUntil + framework { + id + name + } + } +`; + +type Mutation = (p: { + variables: { + input: { + auditId: string; + } & Params; + connections: string[]; + }; +}) => void; + +type Props = { + audits: (LinkedAuditsCardFragment$key & { id: string })[]; + params: Params; + disabled?: boolean; + connectionId: string; + onAttach: Mutation; + onDetach: Mutation; + variant?: "card" | "table"; +}; + +export function LinkedAuditsCard(props: Props) { + const { __ } = useTranslate(); + const [limit, setLimit] = useState(4); + const audits = useMemo(() => { + return limit ? props.audits.slice(0, limit) : props.audits; + }, [props.audits, limit]); + const showMoreButton = limit !== null && props.audits.length > limit; + const variant = props.variant ?? "table"; + + const onAttach = (auditId: string) => { + props.onAttach({ + variables: { + input: { + auditId, + ...props.params, + }, + connections: [props.connectionId], + }, + }); + }; + + const onDetach = (auditId: string) => { + props.onDetach({ + variables: { + input: { + auditId, + ...props.params, + }, + connections: [props.connectionId], + }, + }); + }; + + const Wrapper = variant === "card" ? Card : "div"; + + return ( + + {variant === "card" && ( +
+
{__("Audits")}
+ + + +
+ )} + + + + + + + + + + {audits.length === 0 && ( + + + + )} + {audits.map((audit) => ( + + ))} + {variant === "table" && ( + + + {__("Link audit")} + + + )} + +
{__("Name")}{__("State")}
+ {__("No audits linked")} +
+ {showMoreButton && ( + + )} +
+ ); +} + +function AuditRow(props: { + audit: LinkedAuditsCardFragment$key & { id: string }; + onClick: (auditId: string) => void; +}) { + const audit = useFragment(linkedAuditFragment, props.audit); + const organizationId = useOrganizationId(); + const { __ } = useTranslate(); + + return ( + + +
+
+ {audit.framework?.name} +
+ {audit.name && ( +
+ {audit.name} +
+ )} +
+ + + + {audit.state.replace(/_/g, " ")} + + + + + + + ); +} diff --git a/apps/console/src/components/audits/LinkedAuditsDialog.tsx b/apps/console/src/components/audits/LinkedAuditsDialog.tsx new file mode 100644 index 000000000..e28c8aa0a --- /dev/null +++ b/apps/console/src/components/audits/LinkedAuditsDialog.tsx @@ -0,0 +1,195 @@ +import { + Button, + Dialog, + DialogContent, + DialogFooter, + Badge, + IconMagnifyingGlass, + IconPlusLarge, + IconTrashCan, + InfiniteScrollTrigger, + Input, + Spinner, +} from "@probo/ui"; +import { useTranslate } from "@probo/i18n"; +import { getAuditStateVariant } from "@probo/helpers"; +import { Suspense, useMemo, useState, type ReactNode } from "react"; +import { graphql } from "relay-runtime"; +import { useLazyLoadQuery, usePaginationFragment } from "react-relay"; +import type { LinkedAuditsDialogQuery } from "./__generated__/LinkedAuditsDialogQuery.graphql"; +import { useOrganizationId } from "/hooks/useOrganizationId"; +import type { NodeOf } from "/types"; +import type { + LinkedAuditsDialogFragment$data, + LinkedAuditsDialogFragment$key, +} from "./__generated__/LinkedAuditsDialogFragment.graphql"; + +const auditsQuery = graphql` + query LinkedAuditsDialogQuery($organizationId: ID!) { + organization: node(id: $organizationId) { + id + ... on Organization { + ...LinkedAuditsDialogFragment + } + } + } +`; + +const auditsFragment = graphql` + fragment LinkedAuditsDialogFragment on Organization + @refetchable(queryName: "LinkedAuditsDialogQuery_fragment") + @argumentDefinitions( + first: { type: "Int", defaultValue: 20 } + order: { type: "AuditOrder", defaultValue: null } + after: { type: "CursorKey", defaultValue: null } + before: { type: "CursorKey", defaultValue: null } + last: { type: "Int", defaultValue: null } + ) { + audits( + first: $first + after: $after + last: $last + before: $before + orderBy: $order + ) @connection(key: "LinkedAuditsDialogQuery_audits") { + edges { + node { + id + name + state + validFrom + validUntil + framework { + id + name + } + } + } + } + } +`; + +type Props = { + children: ReactNode; + disabled?: boolean; + linkedAudits?: { id: string }[]; + onLink: (auditId: string) => void; + onUnlink: (auditId: string) => void; +}; + +export function LinkedAuditsDialog({ children, ...props }: Props) { + const { __ } = useTranslate(); + + return ( + + + }> + + + + + + ); +} + +function LinkedAuditsDialogContent(props: Omit) { + const organizationId = useOrganizationId(); + const query = useLazyLoadQuery(auditsQuery, { + organizationId, + }); + const { data, loadNext, hasNext, isLoadingNext } = usePaginationFragment( + auditsFragment, + query.organization as LinkedAuditsDialogFragment$key + ); + const { __ } = useTranslate(); + const [search, setSearch] = useState(""); + const audits = data.audits?.edges?.map((edge) => edge.node) ?? []; + const linkedIds = useMemo(() => { + return new Set(props.linkedAudits?.map((a) => a.id) ?? []); + }, [props.linkedAudits]); + + const filteredAudits = useMemo(() => { + return audits.filter((audit) => + (audit.name || "").toLowerCase().includes(search.toLowerCase()) + ); + }, [audits, search]); + + return ( + <> +
+ +
+
+ {filteredAudits.map((audit) => ( + + ))} + {hasNext && ( + loadNext(20)} + /> + )} +
+ + ); +} + +type Audit = NodeOf; + +type RowProps = { + audit: Audit; + linkedAudits: Set; + disabled?: boolean; + onLink: (auditId: string) => void; + onUnlink: (auditId: string) => void; +}; + +function AuditRow(props: RowProps) { + const { __ } = useTranslate(); + + const isLinked = props.linkedAudits.has(props.audit.id); + const onClick = isLinked ? props.onUnlink : props.onLink; + const IconComponent = isLinked ? IconTrashCan : IconPlusLarge; + + return ( + + + ); +} diff --git a/apps/console/src/components/audits/__generated__/LinkedAuditsCardFragment.graphql.ts b/apps/console/src/components/audits/__generated__/LinkedAuditsCardFragment.graphql.ts new file mode 100644 index 000000000..161bade80 --- /dev/null +++ b/apps/console/src/components/audits/__generated__/LinkedAuditsCardFragment.graphql.ts @@ -0,0 +1,104 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +export type AuditState = "COMPLETED" | "IN_PROGRESS" | "NOT_STARTED" | "OUTDATED" | "REJECTED"; +import { FragmentRefs } from "relay-runtime"; +export type LinkedAuditsCardFragment$data = { + readonly createdAt: any; + readonly framework: { + readonly id: string; + readonly name: string; + }; + readonly id: string; + readonly name: string | null | undefined; + readonly state: AuditState; + readonly validFrom: any | null | undefined; + readonly validUntil: any | null | undefined; + readonly " $fragmentType": "LinkedAuditsCardFragment"; +}; +export type LinkedAuditsCardFragment$key = { + readonly " $data"?: LinkedAuditsCardFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"LinkedAuditsCardFragment">; +}; + +const node: ReaderFragment = (function(){ +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}; +return { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "LinkedAuditsCardFragment", + "selections": [ + (v0/*: any*/), + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "state", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "validFrom", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "validUntil", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "framework", + "plural": false, + "selections": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "storageKey": null + } + ], + "type": "Audit", + "abstractKey": null +}; +})(); + +(node as any).hash = "9ff9367a1f0668a8537bdd5bf5c7233d"; + +export default node; diff --git a/apps/console/src/components/audits/__generated__/LinkedAuditsDialogFragment.graphql.ts b/apps/console/src/components/audits/__generated__/LinkedAuditsDialogFragment.graphql.ts new file mode 100644 index 000000000..73738a420 --- /dev/null +++ b/apps/console/src/components/audits/__generated__/LinkedAuditsDialogFragment.graphql.ts @@ -0,0 +1,257 @@ +/** + * @generated SignedSource<<72efdb68c08c793ff724c9a16d91d29a>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +export type AuditState = "COMPLETED" | "IN_PROGRESS" | "NOT_STARTED" | "OUTDATED" | "REJECTED"; +import { FragmentRefs } from "relay-runtime"; +export type LinkedAuditsDialogFragment$data = { + readonly audits: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly framework: { + readonly id: string; + readonly name: string; + }; + readonly id: string; + readonly name: string | null | undefined; + readonly state: AuditState; + readonly validFrom: any | null | undefined; + readonly validUntil: any | null | undefined; + }; + }>; + }; + readonly id: string; + readonly " $fragmentType": "LinkedAuditsDialogFragment"; +}; +export type LinkedAuditsDialogFragment$key = { + readonly " $data"?: LinkedAuditsDialogFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"LinkedAuditsDialogFragment">; +}; + +import LinkedAuditsDialogQuery_fragment_graphql from './LinkedAuditsDialogQuery_fragment.graphql'; + +const node: ReaderFragment = (function(){ +var v0 = [ + "audits" +], +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}; +return { + "argumentDefinitions": [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "after" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "before" + }, + { + "defaultValue": 20, + "kind": "LocalArgument", + "name": "first" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "last" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "order" + } + ], + "kind": "Fragment", + "metadata": { + "connection": [ + { + "count": null, + "cursor": null, + "direction": "bidirectional", + "path": (v0/*: any*/) + } + ], + "refetch": { + "connection": { + "forward": { + "count": "first", + "cursor": "after" + }, + "backward": { + "count": "last", + "cursor": "before" + }, + "path": (v0/*: any*/) + }, + "fragmentPathInResult": [ + "node" + ], + "operation": LinkedAuditsDialogQuery_fragment_graphql, + "identifierInfo": { + "identifierField": "id", + "identifierQueryVariableName": "id" + } + } + }, + "name": "LinkedAuditsDialogFragment", + "selections": [ + { + "alias": "audits", + "args": [ + { + "kind": "Variable", + "name": "orderBy", + "variableName": "order" + } + ], + "concreteType": "AuditConnection", + "kind": "LinkedField", + "name": "__LinkedAuditsDialogQuery_audits_connection", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "AuditEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Audit", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v1/*: any*/), + (v2/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "state", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "validFrom", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "validUntil", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "framework", + "plural": false, + "selections": [ + (v1/*: any*/), + (v2/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasPreviousPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "startCursor", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + }, + (v1/*: any*/) + ], + "type": "Organization", + "abstractKey": null +}; +})(); + +(node as any).hash = "d58fca9b4fb36a24ded1858b632f5acf"; + +export default node; diff --git a/apps/console/src/components/audits/__generated__/LinkedAuditsDialogQuery.graphql.ts b/apps/console/src/components/audits/__generated__/LinkedAuditsDialogQuery.graphql.ts new file mode 100644 index 000000000..ee21a096d --- /dev/null +++ b/apps/console/src/components/audits/__generated__/LinkedAuditsDialogQuery.graphql.ts @@ -0,0 +1,273 @@ +/** + * @generated SignedSource<<1f9d92c4803d49c216fef6447a59c779>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type LinkedAuditsDialogQuery$variables = { + organizationId: string; +}; +export type LinkedAuditsDialogQuery$data = { + readonly organization: { + readonly id: string; + readonly " $fragmentSpreads": FragmentRefs<"LinkedAuditsDialogFragment">; + }; +}; +export type LinkedAuditsDialogQuery = { + response: LinkedAuditsDialogQuery$data; + variables: LinkedAuditsDialogQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "organizationId" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "organizationId" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v4 = [ + { + "kind": "Literal", + "name": "first", + "value": 20 + } +], +v5 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "LinkedAuditsDialogQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "LinkedAuditsDialogFragment" + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "LinkedAuditsDialogQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": (v4/*: any*/), + "concreteType": "AuditConnection", + "kind": "LinkedField", + "name": "audits", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "AuditEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Audit", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + (v5/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "state", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "validFrom", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "validUntil", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "framework", + "plural": false, + "selections": [ + (v2/*: any*/), + (v5/*: any*/) + ], + "storageKey": null + }, + (v3/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasPreviousPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "startCursor", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "audits(first:20)" + }, + { + "alias": null, + "args": (v4/*: any*/), + "filters": [ + "orderBy" + ], + "handle": "connection", + "key": "LinkedAuditsDialogQuery_audits", + "kind": "LinkedHandle", + "name": "audits" + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "0daa6dc9bebd2677cc0c36e819631f9c", + "id": null, + "metadata": {}, + "name": "LinkedAuditsDialogQuery", + "operationKind": "query", + "text": "query LinkedAuditsDialogQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n ...LinkedAuditsDialogFragment\n }\n }\n}\n\nfragment LinkedAuditsDialogFragment on Organization {\n audits(first: 20) {\n edges {\n node {\n id\n name\n state\n validFrom\n validUntil\n framework {\n id\n name\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n" + } +}; +})(); + +(node as any).hash = "a6f87b771862cc24fee101cc924a57b2"; + +export default node; diff --git a/apps/console/src/components/audits/__generated__/LinkedAuditsDialogQuery_fragment.graphql.ts b/apps/console/src/components/audits/__generated__/LinkedAuditsDialogQuery_fragment.graphql.ts new file mode 100644 index 000000000..ab22ab4f7 --- /dev/null +++ b/apps/console/src/components/audits/__generated__/LinkedAuditsDialogQuery_fragment.graphql.ts @@ -0,0 +1,346 @@ +/** + * @generated SignedSource<<64f01ce9caaee3040df3df201bb608f8>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type AuditOrderField = "CREATED_AT" | "STATE" | "VALID_FROM" | "VALID_UNTIL"; +export type OrderDirection = "ASC" | "DESC"; +export type AuditOrder = { + direction: OrderDirection; + field: AuditOrderField; +}; +export type LinkedAuditsDialogQuery_fragment$variables = { + after?: any | null | undefined; + before?: any | null | undefined; + first?: number | null | undefined; + id: string; + last?: number | null | undefined; + order?: AuditOrder | null | undefined; +}; +export type LinkedAuditsDialogQuery_fragment$data = { + readonly node: { + readonly " $fragmentSpreads": FragmentRefs<"LinkedAuditsDialogFragment">; + }; +}; +export type LinkedAuditsDialogQuery_fragment = { + response: LinkedAuditsDialogQuery_fragment$data; + variables: LinkedAuditsDialogQuery_fragment$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "after" +}, +v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "before" +}, +v2 = { + "defaultValue": 20, + "kind": "LocalArgument", + "name": "first" +}, +v3 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "id" +}, +v4 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "last" +}, +v5 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "order" +}, +v6 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "id" + } +], +v7 = { + "kind": "Variable", + "name": "after", + "variableName": "after" +}, +v8 = { + "kind": "Variable", + "name": "before", + "variableName": "before" +}, +v9 = { + "kind": "Variable", + "name": "first", + "variableName": "first" +}, +v10 = { + "kind": "Variable", + "name": "last", + "variableName": "last" +}, +v11 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v12 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v13 = [ + (v7/*: any*/), + (v8/*: any*/), + (v9/*: any*/), + (v10/*: any*/), + { + "kind": "Variable", + "name": "orderBy", + "variableName": "order" + } +], +v14 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/), + (v2/*: any*/), + (v3/*: any*/), + (v4/*: any*/), + (v5/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "LinkedAuditsDialogQuery_fragment", + "selections": [ + { + "alias": null, + "args": (v6/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "args": [ + (v7/*: any*/), + (v8/*: any*/), + (v9/*: any*/), + (v10/*: any*/), + { + "kind": "Variable", + "name": "order", + "variableName": "order" + } + ], + "kind": "FragmentSpread", + "name": "LinkedAuditsDialogFragment" + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/), + (v2/*: any*/), + (v4/*: any*/), + (v5/*: any*/), + (v3/*: any*/) + ], + "kind": "Operation", + "name": "LinkedAuditsDialogQuery_fragment", + "selections": [ + { + "alias": null, + "args": (v6/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v11/*: any*/), + (v12/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": (v13/*: any*/), + "concreteType": "AuditConnection", + "kind": "LinkedField", + "name": "audits", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "AuditEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Audit", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v12/*: any*/), + (v14/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "state", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "validFrom", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "validUntil", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "framework", + "plural": false, + "selections": [ + (v12/*: any*/), + (v14/*: any*/) + ], + "storageKey": null + }, + (v11/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasPreviousPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "startCursor", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": (v13/*: any*/), + "filters": [ + "orderBy" + ], + "handle": "connection", + "key": "LinkedAuditsDialogQuery_audits", + "kind": "LinkedHandle", + "name": "audits" + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "26f1cad4cab5157a2d9e10d4b89114ec", + "id": null, + "metadata": {}, + "name": "LinkedAuditsDialogQuery_fragment", + "operationKind": "query", + "text": "query LinkedAuditsDialogQuery_fragment(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: AuditOrder = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...LinkedAuditsDialogFragment_16fISc\n id\n }\n}\n\nfragment LinkedAuditsDialogFragment_16fISc on Organization {\n audits(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n edges {\n node {\n id\n name\n state\n validFrom\n validUntil\n framework {\n id\n name\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n" + } +}; +})(); + +(node as any).hash = "d58fca9b4fb36a24ded1858b632f5acf"; + +export default node; diff --git a/apps/console/src/hooks/graph/FrameworkGraph.ts b/apps/console/src/hooks/graph/FrameworkGraph.ts index 9c6793da2..9ae18d9ec 100644 --- a/apps/console/src/hooks/graph/FrameworkGraph.ts +++ b/apps/console/src/hooks/graph/FrameworkGraph.ts @@ -120,6 +120,16 @@ export const frameworkControlNodeQuery = graphql` } } } + audits(first: 100) + @connection(key: "FrameworkGraphControl_audits") { + __id + edges { + node { + id + ...LinkedAuditsCardFragment + } + } + } } } } diff --git a/apps/console/src/hooks/graph/__generated__/FrameworkGraphControlNodeQuery.graphql.ts b/apps/console/src/hooks/graph/__generated__/FrameworkGraphControlNodeQuery.graphql.ts index 507b5b875..367d668f9 100644 --- a/apps/console/src/hooks/graph/__generated__/FrameworkGraphControlNodeQuery.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/FrameworkGraphControlNodeQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<95d2adf687d1cae6938863ba004db9a8>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -16,6 +16,15 @@ export type FrameworkGraphControlNodeQuery$variables = { }; export type FrameworkGraphControlNodeQuery$data = { readonly node: { + readonly audits?: { + readonly __id: string; + readonly edges: ReadonlyArray<{ + readonly node: { + readonly id: string; + readonly " $fragmentSpreads": FragmentRefs<"LinkedAuditsCardFragment">; + }; + }>; + }; readonly description?: string; readonly documents?: { readonly __id: string; @@ -162,7 +171,21 @@ v12 = [ "name": "first", "value": 100 } -]; +], +v13 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "state", + "storageKey": null +}, +v14 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null +}; return { "fragment": { "argumentDefinitions": (v0/*: any*/), @@ -277,6 +300,49 @@ return { (v11/*: any*/) ], "storageKey": null + }, + { + "alias": "audits", + "args": null, + "concreteType": "AuditConnection", + "kind": "LinkedField", + "name": "__FrameworkGraphControl_audits_connection", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "AuditEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Audit", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "args": null, + "kind": "FragmentSpread", + "name": "LinkedAuditsCardFragment" + }, + (v8/*: any*/) + ], + "storageKey": null + }, + (v9/*: any*/) + ], + "storageKey": null + }, + (v10/*: any*/), + (v11/*: any*/) + ], + "storageKey": null } ], "type": "Control", @@ -339,13 +405,7 @@ return { "selections": [ (v2/*: any*/), (v3/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "state", - "storageKey": null - }, + (v13/*: any*/), (v8/*: any*/) ], "storageKey": null @@ -400,13 +460,7 @@ return { "name": "title", "storageKey": null }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "createdAt", - "storageKey": null - }, + (v14/*: any*/), { "alias": null, "args": null, @@ -476,6 +530,83 @@ return { "key": "FrameworkGraphControl_documents", "kind": "LinkedHandle", "name": "documents" + }, + { + "alias": null, + "args": (v12/*: any*/), + "concreteType": "AuditConnection", + "kind": "LinkedField", + "name": "audits", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "AuditEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Audit", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + (v3/*: any*/), + (v14/*: any*/), + (v13/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "validFrom", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "validUntil", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "framework", + "plural": false, + "selections": [ + (v2/*: any*/), + (v3/*: any*/) + ], + "storageKey": null + }, + (v8/*: any*/) + ], + "storageKey": null + }, + (v9/*: any*/) + ], + "storageKey": null + }, + (v10/*: any*/), + (v11/*: any*/) + ], + "storageKey": "audits(first:100)" + }, + { + "alias": null, + "args": (v12/*: any*/), + "filters": null, + "handle": "connection", + "key": "FrameworkGraphControl_audits", + "kind": "LinkedHandle", + "name": "audits" } ], "type": "Control", @@ -487,7 +618,7 @@ return { ] }, "params": { - "cacheID": "ed223aa33ef15ba2e170e8aeae990f8e", + "cacheID": "20a0b80a6b0d796951c7f8c6eca41d3c", "id": null, "metadata": { "connection": [ @@ -508,16 +639,25 @@ return { "node", "documents" ] + }, + { + "count": null, + "cursor": null, + "direction": "forward", + "path": [ + "node", + "audits" + ] } ] }, "name": "FrameworkGraphControlNodeQuery", "operationKind": "query", - "text": "query FrameworkGraphControlNodeQuery(\n $controlId: ID!\n) {\n node(id: $controlId) {\n __typename\n ... on Control {\n id\n name\n sectionTitle\n description\n status\n exclusionJustification\n ...FrameworkControlDialogFragment\n measures(first: 100) {\n edges {\n node {\n id\n ...LinkedMeasuresCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n documents(first: 100) {\n edges {\n node {\n id\n ...LinkedDocumentsCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n\nfragment FrameworkControlDialogFragment on Control {\n id\n name\n description\n sectionTitle\n status\n exclusionJustification\n}\n\nfragment LinkedDocumentsCardFragment on Document {\n id\n title\n createdAt\n documentType\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n" + "text": "query FrameworkGraphControlNodeQuery(\n $controlId: ID!\n) {\n node(id: $controlId) {\n __typename\n ... on Control {\n id\n name\n sectionTitle\n description\n status\n exclusionJustification\n ...FrameworkControlDialogFragment\n measures(first: 100) {\n edges {\n node {\n id\n ...LinkedMeasuresCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n documents(first: 100) {\n edges {\n node {\n id\n ...LinkedDocumentsCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n audits(first: 100) {\n edges {\n node {\n id\n ...LinkedAuditsCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n\nfragment FrameworkControlDialogFragment on Control {\n id\n name\n description\n sectionTitle\n status\n exclusionJustification\n}\n\nfragment LinkedAuditsCardFragment on Audit {\n id\n name\n createdAt\n state\n validFrom\n validUntil\n framework {\n id\n name\n }\n}\n\nfragment LinkedDocumentsCardFragment on Document {\n id\n title\n createdAt\n documentType\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n" } }; })(); -(node as any).hash = "d5c8f5ce17bd227c83fec31d63a82262"; +(node as any).hash = "ac5240126dfbf4882ba2b111e865045d"; export default node; diff --git a/apps/console/src/pages/organizations/audits/AuditDetailsPage.tsx b/apps/console/src/pages/organizations/audits/AuditDetailsPage.tsx index a6787a10d..c132e825b 100644 --- a/apps/console/src/pages/organizations/audits/AuditDetailsPage.tsx +++ b/apps/console/src/pages/organizations/audits/AuditDetailsPage.tsx @@ -28,6 +28,7 @@ import { } from "@probo/ui"; import { useTranslate } from "@probo/i18n"; import { useOrganizationId } from "/hooks/useOrganizationId"; +import { FrameworkLogo } from "/components/FrameworkLogo"; import { ControlledField } from "/components/form/ControlledField"; import { useFormWithSchema } from "/hooks/useFormWithSchema"; import z from "zod"; @@ -135,7 +136,10 @@ export default function AuditDetailsPage(props: Props) {
-
{auditEntry.framework?.name}
+
+ +
{auditEntry.framework?.name}
+
{getAuditStateLabel(__, auditEntry.state || "NOT_STARTED")} diff --git a/apps/console/src/pages/organizations/frameworks/FrameworkControlPage.tsx b/apps/console/src/pages/organizations/frameworks/FrameworkControlPage.tsx index fd60f8530..369ebcf98 100644 --- a/apps/console/src/pages/organizations/frameworks/FrameworkControlPage.tsx +++ b/apps/console/src/pages/organizations/frameworks/FrameworkControlPage.tsx @@ -17,6 +17,7 @@ import { LinkedMeasuresCard } from "/components/measures/LinkedMeasuresCard"; import { useNavigate, useOutletContext } from "react-router"; import { useOrganizationId } from "/hooks/useOrganizationId"; import { LinkedDocumentsCard } from "/components/documents/LinkedDocumentsCard"; +import { LinkedAuditsCard } from "/components/audits/LinkedAuditsCard"; import { FrameworkControlDialog } from "./dialogs/FrameworkControlDialog"; import { promisifyMutation } from "@probo/helpers"; import type { FrameworkGraphControlNodeQuery } from "/hooks/graph/__generated__/FrameworkGraphControlNodeQuery.graphql"; @@ -77,6 +78,33 @@ const detachDocumentMutation = graphql` } `; +const attachAuditMutation = graphql` + mutation FrameworkControlPageAttachAuditMutation( + $input: CreateControlAuditMappingInput! + $connections: [ID!]! + ) { + createControlAuditMapping(input: $input) { + auditEdge @prependEdge(connections: $connections) { + node { + id + ...LinkedAuditsCardFragment + } + } + } + } +`; + +const detachAuditMutation = graphql` + mutation FrameworkControlPageDetachAuditMutation( + $input: DeleteControlAuditMappingInput! + $connections: [ID!]! + ) { + deleteControlAuditMapping(input: $input) { + deletedAuditId @deleteEdge(connections: $connections) + } + } +`; + const deleteControlMutation = graphql` mutation FrameworkControlPageDeleteControlMutation( $input: DeleteControlInput! @@ -118,6 +146,8 @@ export default function FrameworkControlPage({ queryRef }: Props) { const [attachDocument, isAttachingDocument] = useMutation( attachDocumentMutation ); + const [detachAudit, isDetachingAudit] = useMutation(detachAuditMutation); + const [attachAudit, isAttachingAudit] = useMutation(attachAuditMutation); const [deleteControl] = useMutation(deleteControlMutation); const onDelete = () => { @@ -204,7 +234,16 @@ export default function FrameworkControlPage({ queryRef }: Props) { onAttach={attachDocument} onDetach={detachDocument} disabled={isAttachingDocument || isDetachingDocument} - /> + /> + edge.node) ?? []} + params={{ controlId: control.id }} + connectionId={control.audits?.__id!} + onAttach={attachAudit} + onDetach={detachAudit} + disabled={isAttachingAudit || isDetachingAudit} + />
); diff --git a/apps/console/src/pages/organizations/frameworks/__generated__/FrameworkControlPageAttachAuditMutation.graphql.ts b/apps/console/src/pages/organizations/frameworks/__generated__/FrameworkControlPageAttachAuditMutation.graphql.ts new file mode 100644 index 000000000..5cbb66d45 --- /dev/null +++ b/apps/console/src/pages/organizations/frameworks/__generated__/FrameworkControlPageAttachAuditMutation.graphql.ts @@ -0,0 +1,237 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type CreateControlAuditMappingInput = { + auditId: string; + controlId: string; +}; +export type FrameworkControlPageAttachAuditMutation$variables = { + connections: ReadonlyArray; + input: CreateControlAuditMappingInput; +}; +export type FrameworkControlPageAttachAuditMutation$data = { + readonly createControlAuditMapping: { + readonly auditEdge: { + readonly node: { + readonly id: string; + readonly " $fragmentSpreads": FragmentRefs<"LinkedAuditsCardFragment">; + }; + }; + }; +}; +export type FrameworkControlPageAttachAuditMutation = { + response: FrameworkControlPageAttachAuditMutation$data; + variables: FrameworkControlPageAttachAuditMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "connections" +}, +v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" +}, +v2 = [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } +], +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v4 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "FrameworkControlPageAttachAuditMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "CreateControlAuditMappingPayload", + "kind": "LinkedField", + "name": "createControlAuditMapping", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "AuditEdge", + "kind": "LinkedField", + "name": "auditEdge", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Audit", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "args": null, + "kind": "FragmentSpread", + "name": "LinkedAuditsCardFragment" + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "FrameworkControlPageAttachAuditMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "CreateControlAuditMappingPayload", + "kind": "LinkedField", + "name": "createControlAuditMapping", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "AuditEdge", + "kind": "LinkedField", + "name": "auditEdge", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Audit", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + (v4/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "state", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "validFrom", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "validUntil", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Framework", + "kind": "LinkedField", + "name": "framework", + "plural": false, + "selections": [ + (v3/*: any*/), + (v4/*: any*/) + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "filters": null, + "handle": "prependEdge", + "key": "", + "kind": "LinkedHandle", + "name": "auditEdge", + "handleArgs": [ + { + "kind": "Variable", + "name": "connections", + "variableName": "connections" + } + ] + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "23afa68a91d9ae6630123a4ff693a0ec", + "id": null, + "metadata": {}, + "name": "FrameworkControlPageAttachAuditMutation", + "operationKind": "mutation", + "text": "mutation FrameworkControlPageAttachAuditMutation(\n $input: CreateControlAuditMappingInput!\n) {\n createControlAuditMapping(input: $input) {\n auditEdge {\n node {\n id\n ...LinkedAuditsCardFragment\n }\n }\n }\n}\n\nfragment LinkedAuditsCardFragment on Audit {\n id\n name\n createdAt\n state\n validFrom\n validUntil\n framework {\n id\n name\n }\n}\n" + } +}; +})(); + +(node as any).hash = "0a9f52be5fb551ec55667b7d777391de"; + +export default node; diff --git a/apps/console/src/pages/organizations/frameworks/__generated__/FrameworkControlPageDetachAuditMutation.graphql.ts b/apps/console/src/pages/organizations/frameworks/__generated__/FrameworkControlPageDetachAuditMutation.graphql.ts new file mode 100644 index 000000000..7c49bb0f7 --- /dev/null +++ b/apps/console/src/pages/organizations/frameworks/__generated__/FrameworkControlPageDetachAuditMutation.graphql.ts @@ -0,0 +1,133 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type DeleteControlAuditMappingInput = { + auditId: string; + controlId: string; +}; +export type FrameworkControlPageDetachAuditMutation$variables = { + connections: ReadonlyArray; + input: DeleteControlAuditMappingInput; +}; +export type FrameworkControlPageDetachAuditMutation$data = { + readonly deleteControlAuditMapping: { + readonly deletedAuditId: string; + }; +}; +export type FrameworkControlPageDetachAuditMutation = { + response: FrameworkControlPageDetachAuditMutation$data; + variables: FrameworkControlPageDetachAuditMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "connections" +}, +v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" +}, +v2 = [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } +], +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "deletedAuditId", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "FrameworkControlPageDetachAuditMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "DeleteControlAuditMappingPayload", + "kind": "LinkedField", + "name": "deleteControlAuditMapping", + "plural": false, + "selections": [ + (v3/*: any*/) + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "FrameworkControlPageDetachAuditMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "DeleteControlAuditMappingPayload", + "kind": "LinkedField", + "name": "deleteControlAuditMapping", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "filters": null, + "handle": "deleteEdge", + "key": "", + "kind": "ScalarHandle", + "name": "deletedAuditId", + "handleArgs": [ + { + "kind": "Variable", + "name": "connections", + "variableName": "connections" + } + ] + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "0ec6a285e6ffd0057602e30ef8411e16", + "id": null, + "metadata": {}, + "name": "FrameworkControlPageDetachAuditMutation", + "operationKind": "mutation", + "text": "mutation FrameworkControlPageDetachAuditMutation(\n $input: DeleteControlAuditMappingInput!\n) {\n deleteControlAuditMapping(input: $input) {\n deletedAuditId\n }\n}\n" + } +}; +})(); + +(node as any).hash = "3c800764c7d5801bd228e8a61625bd75"; + +export default node; diff --git a/pkg/coredata/audit.go b/pkg/coredata/audit.go index 9e8611e9d..1119b3ba1 100644 --- a/pkg/coredata/audit.go +++ b/pkg/coredata/audit.go @@ -314,3 +314,109 @@ WHERE return nil } + +func (a *Audits) CountByControlID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + controlID gid.GID, +) (int, error) { + q := ` +WITH audits_by_control AS ( + SELECT + a.id, + a.tenant_id + FROM + audits a + INNER JOIN + controls_audits ca ON a.id = ca.audit_id + WHERE + ca.control_id = @control_id + ) + SELECT + COUNT(id) + FROM + audits_by_control + WHERE %s + ` + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{"control_id": controlID} + maps.Copy(args, scope.SQLArguments()) + + row := conn.QueryRow(ctx, q, args) + + var count int + if err := row.Scan(&count); err != nil { + return 0, fmt.Errorf("cannot scan count: %w", err) + } + + return count, nil +} + +func (a *Audits) LoadByControlID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + controlID gid.GID, + cursor *page.Cursor[AuditOrderField], +) error { + q := ` +WITH audits_by_control AS ( + SELECT + a.id, + a.tenant_id, + a.name, + a.organization_id, + a.framework_id, + a.report_id, + a.valid_from, + a.valid_until, + a.state, + a.show_on_trust_center, + a.created_at, + a.updated_at + FROM + audits a + INNER JOIN + controls_audits ca ON a.id = ca.audit_id + WHERE + ca.control_id = @control_id +) +SELECT + id, + name, + organization_id, + framework_id, + report_id, + valid_from, + valid_until, + state, + show_on_trust_center, + created_at, + updated_at +FROM + audits_by_control +WHERE %s + AND %s +` + q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) + + args := pgx.NamedArgs{"control_id": controlID} + maps.Copy(args, scope.SQLArguments()) + maps.Copy(args, cursor.SQLArguments()) + + rows, err := conn.Query(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot query audits: %w", err) + } + + audits, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Audit]) + if err != nil { + return fmt.Errorf("cannot collect audits: %w", err) + } + + *a = audits + + return nil +} diff --git a/pkg/coredata/control.go b/pkg/coredata/control.go index 6ce711917..470ea48d2 100644 --- a/pkg/coredata/control.go +++ b/pkg/coredata/control.go @@ -826,3 +826,114 @@ RETURNING return nil } + +func (c *Controls) CountByAuditID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + auditID gid.GID, + filter *ControlFilter, +) (int, error) { + q := ` +WITH ctrl AS ( + SELECT + c.id, + c.tenant_id, + c.search_vector + FROM + controls c + INNER JOIN + controls_audits ca ON c.id = ca.control_id + WHERE + ca.audit_id = @audit_id + ) + SELECT + COUNT(id) + FROM + ctrl + WHERE %s + AND %s + ` + q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment()) + + args := pgx.NamedArgs{"audit_id": auditID} + maps.Copy(args, scope.SQLArguments()) + maps.Copy(args, filter.SQLArguments()) + + row := conn.QueryRow(ctx, q, args) + + var count int + if err := row.Scan(&count); err != nil { + return 0, fmt.Errorf("cannot scan count: %w", err) + } + + return count, nil +} + +func (c *Controls) LoadByAuditID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + auditID gid.GID, + cursor *page.Cursor[ControlOrderField], + filter *ControlFilter, +) error { + q := ` +WITH ctrl AS ( + SELECT + c.id, + c.section_title, + c.framework_id, + c.tenant_id, + c.name, + c.description, + c.status, + c.exclusion_justification, + c.created_at, + c.updated_at, + c.search_vector + FROM + controls c + INNER JOIN + controls_audits ca ON c.id = ca.control_id + WHERE + ca.audit_id = @audit_id +) +SELECT + id, + section_title, + framework_id, + tenant_id, + name, + description, + status, + exclusion_justification, + created_at, + updated_at +FROM + ctrl +WHERE %s + AND %s + AND %s +` + q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment()) + + args := pgx.NamedArgs{"audit_id": auditID} + maps.Copy(args, scope.SQLArguments()) + maps.Copy(args, filter.SQLArguments()) + maps.Copy(args, cursor.SQLArguments()) + + rows, err := conn.Query(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot query controls: %w", err) + } + + controls, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Control]) + if err != nil { + return fmt.Errorf("cannot collect controls: %w", err) + } + + *c = controls + + return nil +} diff --git a/pkg/coredata/control_audit.go b/pkg/coredata/control_audit.go new file mode 100644 index 000000000..6709925a6 --- /dev/null +++ b/pkg/coredata/control_audit.go @@ -0,0 +1,168 @@ +// Copyright (c) 2025 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package coredata + +import ( + "context" + "fmt" + "maps" + "time" + + "github.com/getprobo/probo/pkg/gid" + "github.com/jackc/pgx/v5" + "go.gearno.de/kit/pg" +) + +type ( + ControlAudit struct { + ControlID gid.GID `db:"control_id"` + AuditID gid.GID `db:"audit_id"` + CreatedAt time.Time `db:"created_at"` + } + + ControlAudits []*ControlAudit +) + +func (ca ControlAudit) Upsert( + ctx context.Context, + conn pg.Conn, + scope Scoper, +) error { + q := ` +INSERT INTO + controls_audits ( + control_id, + audit_id, + tenant_id, + created_at + ) +VALUES ( + @control_id, + @audit_id, + @tenant_id, + @created_at +) +ON CONFLICT (control_id, audit_id) DO NOTHING; +` + + args := pgx.StrictNamedArgs{ + "control_id": ca.ControlID, + "audit_id": ca.AuditID, + "tenant_id": scope.GetTenantID(), + "created_at": ca.CreatedAt, + } + _, err := conn.Exec(ctx, q, args) + return err +} + +func (ca ControlAudit) Delete( + ctx context.Context, + conn pg.Conn, + scope Scoper, + controlID gid.GID, + auditID gid.GID, +) error { + q := ` +DELETE +FROM + controls_audits +WHERE + %s + AND control_id = @control_id + AND audit_id = @audit_id; +` + + args := pgx.StrictNamedArgs{ + "control_id": controlID, + "audit_id": auditID, + } + maps.Copy(args, scope.SQLArguments()) + q = fmt.Sprintf(q, scope.SQLFragment()) + + _, err := conn.Exec(ctx, q, args) + return err +} + +func (cas *ControlAudits) LoadByControlID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + controlID gid.GID, +) error { + q := ` +SELECT + control_id, + audit_id, + created_at +FROM + controls_audits +WHERE + %s + AND control_id = @control_id +` + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{"control_id": controlID} + maps.Copy(args, scope.SQLArguments()) + + rows, err := conn.Query(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot query control_audits: %w", err) + } + + controlAudits, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[ControlAudit]) + if err != nil { + return fmt.Errorf("cannot collect control_audits: %w", err) + } + + *cas = controlAudits + return nil +} + +func (cas *ControlAudits) LoadByAuditID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + auditID gid.GID, +) error { + q := ` +SELECT + control_id, + audit_id, + created_at +FROM + controls_audits +WHERE + %s + AND audit_id = @audit_id +` + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{"audit_id": auditID} + maps.Copy(args, scope.SQLArguments()) + + rows, err := conn.Query(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot query control_audits: %w", err) + } + + controlAudits, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[ControlAudit]) + if err != nil { + return fmt.Errorf("cannot collect control_audits: %w", err) + } + + *cas = controlAudits + return nil +} diff --git a/pkg/coredata/migrations/20250814T123441Z.sql b/pkg/coredata/migrations/20250814T123441Z.sql new file mode 100644 index 000000000..d6927eade --- /dev/null +++ b/pkg/coredata/migrations/20250814T123441Z.sql @@ -0,0 +1,7 @@ +CREATE TABLE controls_audits ( + control_id TEXT NOT NULL REFERENCES controls(id), + audit_id TEXT NOT NULL REFERENCES audits(id), + tenant_id TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE NOT NULL, + PRIMARY KEY (control_id, audit_id) +); diff --git a/pkg/probo/audit_service.go b/pkg/probo/audit_service.go index d046be101..0f28c1a66 100644 --- a/pkg/probo/audit_service.go +++ b/pkg/probo/audit_service.go @@ -346,3 +346,34 @@ func (s AuditService) DeleteReport( return audit, nil } + +func (s AuditService) ListForControlID( + ctx context.Context, + controlID gid.GID, + cursor *page.Cursor[coredata.AuditOrderField], +) (*page.Page[*coredata.Audit, coredata.AuditOrderField], error) { + var audits coredata.Audits + control := &coredata.Control{} + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) error { + if err := control.LoadByID(ctx, conn, s.svc.scope, controlID); err != nil { + return fmt.Errorf("cannot load control: %w", err) + } + + err := audits.LoadByControlID(ctx, conn, s.svc.scope, control.ID, cursor) + if err != nil { + return fmt.Errorf("cannot load audits: %w", err) + } + + return nil + }, + ) + + if err != nil { + return nil, err + } + + return page.NewPage(audits, cursor), nil +} diff --git a/pkg/probo/control_service.go b/pkg/probo/control_service.go index c25a3393d..dabe82a14 100644 --- a/pkg/probo/control_service.go +++ b/pkg/probo/control_service.go @@ -492,6 +492,111 @@ func (s ControlService) DeleteDocumentMapping( return control, document, nil } +func (s ControlService) CreateAuditMapping( + ctx context.Context, + controlID gid.GID, + auditID gid.GID, +) (*coredata.Control, *coredata.Audit, error) { + controlAudit := &coredata.ControlAudit{ + ControlID: controlID, + AuditID: auditID, + CreatedAt: time.Now(), + } + + control := &coredata.Control{} + audit := &coredata.Audit{} + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) error { + if err := control.LoadByID(ctx, conn, s.svc.scope, controlID); err != nil { + return fmt.Errorf("cannot load control: %w", err) + } + + if err := audit.LoadByID(ctx, conn, s.svc.scope, auditID); err != nil { + return fmt.Errorf("cannot load audit: %w", err) + } + + if err := controlAudit.Upsert(ctx, conn, s.svc.scope); err != nil { + return fmt.Errorf("cannot create control audit mapping: %w", err) + } + + return nil + }, + ) + + if err != nil { + return nil, nil, err + } + + return control, audit, nil +} + +func (s ControlService) DeleteAuditMapping( + ctx context.Context, + controlID gid.GID, + auditID gid.GID, +) (*coredata.Control, *coredata.Audit, error) { + control := &coredata.Control{} + audit := &coredata.Audit{} + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) error { + if err := control.LoadByID(ctx, conn, s.svc.scope, controlID); err != nil { + return fmt.Errorf("cannot load control: %w", err) + } + + if err := audit.LoadByID(ctx, conn, s.svc.scope, auditID); err != nil { + return fmt.Errorf("cannot load audit: %w", err) + } + + controlAudit := &coredata.ControlAudit{} + if err := controlAudit.Delete(ctx, conn, s.svc.scope, control.ID, audit.ID); err != nil { + return fmt.Errorf("cannot delete control audit mapping: %w", err) + } + + return nil + }, + ) + + if err != nil { + return nil, nil, fmt.Errorf("cannot delete control audit mapping: %w", err) + } + + return control, audit, nil +} + +func (s ControlService) ListForAuditID( + ctx context.Context, + auditID gid.GID, + cursor *page.Cursor[coredata.ControlOrderField], + filter *coredata.ControlFilter, +) (*page.Page[*coredata.Control, coredata.ControlOrderField], error) { + var controls coredata.Controls + audit := &coredata.Audit{} + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) error { + if err := audit.LoadByID(ctx, conn, s.svc.scope, auditID); err != nil { + return fmt.Errorf("cannot load audit: %w", err) + } + if err := controls.LoadByAuditID(ctx, conn, s.svc.scope, auditID, cursor, filter); err != nil { + return fmt.Errorf("cannot load controls: %w", err) + } + + return nil + }, + ) + + if err != nil { + return nil, err + } + + return page.NewPage([]*coredata.Control(controls), cursor), nil +} + func (s ControlService) Create( ctx context.Context, req CreateControlRequest, diff --git a/pkg/server/api/console/v1/schema.graphql b/pkg/server/api/console/v1/schema.graphql index fb3199b86..377188ea3 100644 --- a/pkg/server/api/console/v1/schema.graphql +++ b/pkg/server/api/console/v1/schema.graphql @@ -1076,6 +1076,14 @@ type Control implements Node { filter: DocumentFilter ): DocumentConnection! @goField(forceResolver: true) + audits( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: AuditOrder + ): AuditConnection! @goField(forceResolver: true) + createdAt: Datetime! updatedAt: Datetime! } @@ -1257,6 +1265,16 @@ type Audit implements Node { report: Report @goField(forceResolver: true) reportUrl: String @goField(forceResolver: true) state: AuditState! + + controls( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ControlOrder + filter: ControlFilter + ): ControlConnection! @goField(forceResolver: true) + showOnTrustCenter: Boolean! createdAt: Datetime! updatedAt: Datetime! @@ -1636,6 +1654,12 @@ type Mutation { deleteControlDocumentMapping( input: DeleteControlDocumentMappingInput! ): DeleteControlDocumentMappingPayload! + createControlAuditMapping( + input: CreateControlAuditMappingInput! + ): CreateControlAuditMappingPayload! + deleteControlAuditMapping( + input: DeleteControlAuditMappingInput! + ): DeleteControlAuditMappingPayload! # Task mutations createTask(input: CreateTaskInput!): CreateTaskPayload! @@ -1987,6 +2011,16 @@ input DeleteControlDocumentMappingInput { documentId: ID! } +input CreateControlAuditMappingInput { + controlId: ID! + auditId: ID! +} + +input DeleteControlAuditMappingInput { + controlId: ID! + auditId: ID! +} + input CreateRiskInput { organizationId: ID! name: String! @@ -2352,6 +2386,16 @@ type DeleteControlDocumentMappingPayload { deletedDocumentId: ID! } +type CreateControlAuditMappingPayload { + controlEdge: ControlEdge! + auditEdge: AuditEdge! +} + +type DeleteControlAuditMappingPayload { + deletedControlId: ID! + deletedAuditId: ID! +} + type CreateRiskPayload { riskEdge: RiskEdge! } diff --git a/pkg/server/api/console/v1/schema/schema.go b/pkg/server/api/console/v1/schema/schema.go index b71e2c603..66ce4e361 100644 --- a/pkg/server/api/console/v1/schema/schema.go +++ b/pkg/server/api/console/v1/schema/schema.go @@ -121,6 +121,7 @@ type ComplexityRoot struct { } Audit struct { + Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) int CreatedAt func(childComplexity int) int Framework func(childComplexity int) int ID func(childComplexity int) int @@ -182,6 +183,7 @@ type ComplexityRoot struct { } Control struct { + Audits func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditOrderBy) int CreatedAt func(childComplexity int) int Description func(childComplexity int) int Documents func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy, filter *types.DocumentFilter) int @@ -214,6 +216,11 @@ type ComplexityRoot struct { AuditEdge func(childComplexity int) int } + CreateControlAuditMappingPayload struct { + AuditEdge func(childComplexity int) int + ControlEdge func(childComplexity int) int + } + CreateControlDocumentMappingPayload struct { ControlEdge func(childComplexity int) int DocumentEdge func(childComplexity int) int @@ -329,6 +336,11 @@ type ComplexityRoot struct { Audit func(childComplexity int) int } + DeleteControlAuditMappingPayload struct { + DeletedAuditID func(childComplexity int) int + DeletedControlID func(childComplexity int) int + } + DeleteControlDocumentMappingPayload struct { DeletedControlID func(childComplexity int) int DeletedDocumentID func(childComplexity int) int @@ -602,6 +614,7 @@ type ComplexityRoot struct { CreateAsset func(childComplexity int, input types.CreateAssetInput) int CreateAudit func(childComplexity int, input types.CreateAuditInput) int CreateControl func(childComplexity int, input types.CreateControlInput) int + CreateControlAuditMapping func(childComplexity int, input types.CreateControlAuditMappingInput) int CreateControlDocumentMapping func(childComplexity int, input types.CreateControlDocumentMappingInput) int CreateControlMeasureMapping func(childComplexity int, input types.CreateControlMeasureMappingInput) int CreateDatum func(childComplexity int, input types.CreateDatumInput) int @@ -623,6 +636,7 @@ type ComplexityRoot struct { DeleteAudit func(childComplexity int, input types.DeleteAuditInput) int DeleteAuditReport func(childComplexity int, input types.DeleteAuditReportInput) int DeleteControl func(childComplexity int, input types.DeleteControlInput) int + DeleteControlAuditMapping func(childComplexity int, input types.DeleteControlAuditMappingInput) int DeleteControlDocumentMapping func(childComplexity int, input types.DeleteControlDocumentMappingInput) int DeleteControlMeasureMapping func(childComplexity int, input types.DeleteControlMeasureMappingInput) int DeleteDatum func(childComplexity int, input types.DeleteDatumInput) int @@ -1155,6 +1169,8 @@ type AuditResolver interface { Report(ctx context.Context, obj *types.Audit) (*types.Report, error) ReportURL(ctx context.Context, obj *types.Audit) (*string, error) + + Controls(ctx context.Context, obj *types.Audit, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) } type AuditConnectionResolver interface { TotalCount(ctx context.Context, obj *types.AuditConnection) (int, error) @@ -1163,6 +1179,7 @@ type ControlResolver interface { Framework(ctx context.Context, obj *types.Control) (*types.Framework, error) Measures(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.MeasureOrderBy, filter *types.MeasureFilter) (*types.MeasureConnection, error) Documents(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy, filter *types.DocumentFilter) (*types.DocumentConnection, error) + Audits(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditOrderBy) (*types.AuditConnection, error) } type ControlConnectionResolver interface { TotalCount(ctx context.Context, obj *types.ControlConnection) (int, error) @@ -1258,6 +1275,8 @@ type MutationResolver interface { CreateControlDocumentMapping(ctx context.Context, input types.CreateControlDocumentMappingInput) (*types.CreateControlDocumentMappingPayload, error) DeleteControlMeasureMapping(ctx context.Context, input types.DeleteControlMeasureMappingInput) (*types.DeleteControlMeasureMappingPayload, error) DeleteControlDocumentMapping(ctx context.Context, input types.DeleteControlDocumentMappingInput) (*types.DeleteControlDocumentMappingPayload, error) + CreateControlAuditMapping(ctx context.Context, input types.CreateControlAuditMappingInput) (*types.CreateControlAuditMappingPayload, error) + DeleteControlAuditMapping(ctx context.Context, input types.DeleteControlAuditMappingInput) (*types.DeleteControlAuditMappingPayload, error) CreateTask(ctx context.Context, input types.CreateTaskInput) (*types.CreateTaskPayload, error) UpdateTask(ctx context.Context, input types.UpdateTaskInput) (*types.UpdateTaskPayload, error) DeleteTask(ctx context.Context, input types.DeleteTaskInput) (*types.DeleteTaskPayload, error) @@ -1555,6 +1574,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.AssignTaskPayload.Task(childComplexity), true + case "Audit.controls": + if e.complexity.Audit.Controls == nil { + break + } + + args, err := ec.field_Audit_controls_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Audit.Controls(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.ControlOrderBy), args["filter"].(*types.ControlFilter)), true + case "Audit.createdAt": if e.complexity.Audit.CreatedAt == nil { break @@ -1772,6 +1803,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.ConnectorEdge.Node(childComplexity), true + case "Control.audits": + if e.complexity.Control.Audits == nil { + break + } + + args, err := ec.field_Control_audits_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Control.Audits(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.AuditOrderBy)), true + case "Control.createdAt": if e.complexity.Control.CreatedAt == nil { break @@ -1908,6 +1951,20 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.CreateAuditPayload.AuditEdge(childComplexity), true + case "CreateControlAuditMappingPayload.auditEdge": + if e.complexity.CreateControlAuditMappingPayload.AuditEdge == nil { + break + } + + return e.complexity.CreateControlAuditMappingPayload.AuditEdge(childComplexity), true + + case "CreateControlAuditMappingPayload.controlEdge": + if e.complexity.CreateControlAuditMappingPayload.ControlEdge == nil { + break + } + + return e.complexity.CreateControlAuditMappingPayload.ControlEdge(childComplexity), true + case "CreateControlDocumentMappingPayload.controlEdge": if e.complexity.CreateControlDocumentMappingPayload.ControlEdge == nil { break @@ -2193,6 +2250,20 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.DeleteAuditReportPayload.Audit(childComplexity), true + case "DeleteControlAuditMappingPayload.deletedAuditId": + if e.complexity.DeleteControlAuditMappingPayload.DeletedAuditID == nil { + break + } + + return e.complexity.DeleteControlAuditMappingPayload.DeletedAuditID(childComplexity), true + + case "DeleteControlAuditMappingPayload.deletedControlId": + if e.complexity.DeleteControlAuditMappingPayload.DeletedControlID == nil { + break + } + + return e.complexity.DeleteControlAuditMappingPayload.DeletedControlID(childComplexity), true + case "DeleteControlDocumentMappingPayload.deletedControlId": if e.complexity.DeleteControlDocumentMappingPayload.DeletedControlID == nil { break @@ -3216,6 +3287,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.Mutation.CreateControl(childComplexity, args["input"].(types.CreateControlInput)), true + case "Mutation.createControlAuditMapping": + if e.complexity.Mutation.CreateControlAuditMapping == nil { + break + } + + args, err := ec.field_Mutation_createControlAuditMapping_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.CreateControlAuditMapping(childComplexity, args["input"].(types.CreateControlAuditMappingInput)), true + case "Mutation.createControlDocumentMapping": if e.complexity.Mutation.CreateControlDocumentMapping == nil { break @@ -3468,6 +3551,18 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.Mutation.DeleteControl(childComplexity, args["input"].(types.DeleteControlInput)), true + case "Mutation.deleteControlAuditMapping": + if e.complexity.Mutation.DeleteControlAuditMapping == nil { + break + } + + args, err := ec.field_Mutation_deleteControlAuditMapping_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.DeleteControlAuditMapping(childComplexity, args["input"].(types.DeleteControlAuditMappingInput)), true + case "Mutation.deleteControlDocumentMapping": if e.complexity.Mutation.DeleteControlDocumentMapping == nil { break @@ -6029,6 +6124,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputControlOrder, ec.unmarshalInputCreateAssetInput, ec.unmarshalInputCreateAuditInput, + ec.unmarshalInputCreateControlAuditMappingInput, ec.unmarshalInputCreateControlDocumentMappingInput, ec.unmarshalInputCreateControlInput, ec.unmarshalInputCreateControlMeasureMappingInput, @@ -6052,6 +6148,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputDeleteAssetInput, ec.unmarshalInputDeleteAuditInput, ec.unmarshalInputDeleteAuditReportInput, + ec.unmarshalInputDeleteControlAuditMappingInput, ec.unmarshalInputDeleteControlDocumentMappingInput, ec.unmarshalInputDeleteControlInput, ec.unmarshalInputDeleteControlMeasureMappingInput, @@ -7307,6 +7404,14 @@ type Control implements Node { filter: DocumentFilter ): DocumentConnection! @goField(forceResolver: true) + audits( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: AuditOrder + ): AuditConnection! @goField(forceResolver: true) + createdAt: Datetime! updatedAt: Datetime! } @@ -7488,6 +7593,16 @@ type Audit implements Node { report: Report @goField(forceResolver: true) reportUrl: String @goField(forceResolver: true) state: AuditState! + + controls( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: ControlOrder + filter: ControlFilter + ): ControlConnection! @goField(forceResolver: true) + showOnTrustCenter: Boolean! createdAt: Datetime! updatedAt: Datetime! @@ -7867,6 +7982,12 @@ type Mutation { deleteControlDocumentMapping( input: DeleteControlDocumentMappingInput! ): DeleteControlDocumentMappingPayload! + createControlAuditMapping( + input: CreateControlAuditMappingInput! + ): CreateControlAuditMappingPayload! + deleteControlAuditMapping( + input: DeleteControlAuditMappingInput! + ): DeleteControlAuditMappingPayload! # Task mutations createTask(input: CreateTaskInput!): CreateTaskPayload! @@ -8218,6 +8339,16 @@ input DeleteControlDocumentMappingInput { documentId: ID! } +input CreateControlAuditMappingInput { + controlId: ID! + auditId: ID! +} + +input DeleteControlAuditMappingInput { + controlId: ID! + auditId: ID! +} + input CreateRiskInput { organizationId: ID! name: String! @@ -8583,6 +8714,16 @@ type DeleteControlDocumentMappingPayload { deletedDocumentId: ID! } +type CreateControlAuditMappingPayload { + controlEdge: ControlEdge! + auditEdge: AuditEdge! +} + +type DeleteControlAuditMappingPayload { + deletedControlId: ID! + deletedAuditId: ID! +} + type CreateRiskPayload { riskEdge: RiskEdge! } @@ -9201,6 +9342,214 @@ func (ec *executionContext) field_Asset_vendors_argsOrderBy( return zeroVal, nil } +func (ec *executionContext) field_Audit_controls_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Audit_controls_argsFirst(ctx, rawArgs) + if err != nil { + return nil, err + } + args["first"] = arg0 + arg1, err := ec.field_Audit_controls_argsAfter(ctx, rawArgs) + if err != nil { + return nil, err + } + args["after"] = arg1 + arg2, err := ec.field_Audit_controls_argsLast(ctx, rawArgs) + if err != nil { + return nil, err + } + args["last"] = arg2 + arg3, err := ec.field_Audit_controls_argsBefore(ctx, rawArgs) + if err != nil { + return nil, err + } + args["before"] = arg3 + arg4, err := ec.field_Audit_controls_argsOrderBy(ctx, rawArgs) + if err != nil { + return nil, err + } + args["orderBy"] = arg4 + arg5, err := ec.field_Audit_controls_argsFilter(ctx, rawArgs) + if err != nil { + return nil, err + } + args["filter"] = arg5 + return args, nil +} +func (ec *executionContext) field_Audit_controls_argsFirst( + ctx context.Context, + rawArgs map[string]any, +) (*int, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("first")) + if tmp, ok := rawArgs["first"]; ok { + return ec.unmarshalOInt2ᚖint(ctx, tmp) + } + + var zeroVal *int + return zeroVal, nil +} + +func (ec *executionContext) field_Audit_controls_argsAfter( + ctx context.Context, + rawArgs map[string]any, +) (*page.CursorKey, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after")) + if tmp, ok := rawArgs["after"]; ok { + return ec.unmarshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋpageᚐCursorKey(ctx, tmp) + } + + var zeroVal *page.CursorKey + return zeroVal, nil +} + +func (ec *executionContext) field_Audit_controls_argsLast( + ctx context.Context, + rawArgs map[string]any, +) (*int, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("last")) + if tmp, ok := rawArgs["last"]; ok { + return ec.unmarshalOInt2ᚖint(ctx, tmp) + } + + var zeroVal *int + return zeroVal, nil +} + +func (ec *executionContext) field_Audit_controls_argsBefore( + ctx context.Context, + rawArgs map[string]any, +) (*page.CursorKey, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before")) + if tmp, ok := rawArgs["before"]; ok { + return ec.unmarshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋpageᚐCursorKey(ctx, tmp) + } + + var zeroVal *page.CursorKey + return zeroVal, nil +} + +func (ec *executionContext) field_Audit_controls_argsOrderBy( + ctx context.Context, + rawArgs map[string]any, +) (*types.ControlOrderBy, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("orderBy")) + if tmp, ok := rawArgs["orderBy"]; ok { + return ec.unmarshalOControlOrder2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐControlOrderBy(ctx, tmp) + } + + var zeroVal *types.ControlOrderBy + return zeroVal, nil +} + +func (ec *executionContext) field_Audit_controls_argsFilter( + ctx context.Context, + rawArgs map[string]any, +) (*types.ControlFilter, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("filter")) + if tmp, ok := rawArgs["filter"]; ok { + return ec.unmarshalOControlFilter2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐControlFilter(ctx, tmp) + } + + var zeroVal *types.ControlFilter + return zeroVal, nil +} + +func (ec *executionContext) field_Control_audits_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Control_audits_argsFirst(ctx, rawArgs) + if err != nil { + return nil, err + } + args["first"] = arg0 + arg1, err := ec.field_Control_audits_argsAfter(ctx, rawArgs) + if err != nil { + return nil, err + } + args["after"] = arg1 + arg2, err := ec.field_Control_audits_argsLast(ctx, rawArgs) + if err != nil { + return nil, err + } + args["last"] = arg2 + arg3, err := ec.field_Control_audits_argsBefore(ctx, rawArgs) + if err != nil { + return nil, err + } + args["before"] = arg3 + arg4, err := ec.field_Control_audits_argsOrderBy(ctx, rawArgs) + if err != nil { + return nil, err + } + args["orderBy"] = arg4 + return args, nil +} +func (ec *executionContext) field_Control_audits_argsFirst( + ctx context.Context, + rawArgs map[string]any, +) (*int, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("first")) + if tmp, ok := rawArgs["first"]; ok { + return ec.unmarshalOInt2ᚖint(ctx, tmp) + } + + var zeroVal *int + return zeroVal, nil +} + +func (ec *executionContext) field_Control_audits_argsAfter( + ctx context.Context, + rawArgs map[string]any, +) (*page.CursorKey, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after")) + if tmp, ok := rawArgs["after"]; ok { + return ec.unmarshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋpageᚐCursorKey(ctx, tmp) + } + + var zeroVal *page.CursorKey + return zeroVal, nil +} + +func (ec *executionContext) field_Control_audits_argsLast( + ctx context.Context, + rawArgs map[string]any, +) (*int, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("last")) + if tmp, ok := rawArgs["last"]; ok { + return ec.unmarshalOInt2ᚖint(ctx, tmp) + } + + var zeroVal *int + return zeroVal, nil +} + +func (ec *executionContext) field_Control_audits_argsBefore( + ctx context.Context, + rawArgs map[string]any, +) (*page.CursorKey, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before")) + if tmp, ok := rawArgs["before"]; ok { + return ec.unmarshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋpageᚐCursorKey(ctx, tmp) + } + + var zeroVal *page.CursorKey + return zeroVal, nil +} + +func (ec *executionContext) field_Control_audits_argsOrderBy( + ctx context.Context, + rawArgs map[string]any, +) (*types.AuditOrderBy, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("orderBy")) + if tmp, ok := rawArgs["orderBy"]; ok { + return ec.unmarshalOAuditOrder2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐAuditOrderBy(ctx, tmp) + } + + var zeroVal *types.AuditOrderBy + return zeroVal, nil +} + func (ec *executionContext) field_Control_documents_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -10556,6 +10905,29 @@ func (ec *executionContext) field_Mutation_createAudit_argsInput( return zeroVal, nil } +func (ec *executionContext) field_Mutation_createControlAuditMapping_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Mutation_createControlAuditMapping_argsInput(ctx, rawArgs) + if err != nil { + return nil, err + } + args["input"] = arg0 + return args, nil +} +func (ec *executionContext) field_Mutation_createControlAuditMapping_argsInput( + ctx context.Context, + rawArgs map[string]any, +) (types.CreateControlAuditMappingInput, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input")) + if tmp, ok := rawArgs["input"]; ok { + return ec.unmarshalNCreateControlAuditMappingInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐCreateControlAuditMappingInput(ctx, tmp) + } + + var zeroVal types.CreateControlAuditMappingInput + return zeroVal, nil +} + func (ec *executionContext) field_Mutation_createControlDocumentMapping_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -11039,6 +11411,29 @@ func (ec *executionContext) field_Mutation_deleteAudit_argsInput( return zeroVal, nil } +func (ec *executionContext) field_Mutation_deleteControlAuditMapping_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Mutation_deleteControlAuditMapping_argsInput(ctx, rawArgs) + if err != nil { + return nil, err + } + args["input"] = arg0 + return args, nil +} +func (ec *executionContext) field_Mutation_deleteControlAuditMapping_argsInput( + ctx context.Context, + rawArgs map[string]any, +) (types.DeleteControlAuditMappingInput, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input")) + if tmp, ok := rawArgs["input"]; ok { + return ec.unmarshalNDeleteControlAuditMappingInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteControlAuditMappingInput(ctx, tmp) + } + + var zeroVal types.DeleteControlAuditMappingInput + return zeroVal, nil +} + func (ec *executionContext) field_Mutation_deleteControlDocumentMapping_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -16318,6 +16713,69 @@ func (ec *executionContext) fieldContext_Audit_state(_ context.Context, field gr return fc, nil } +func (ec *executionContext) _Audit_controls(ctx context.Context, field graphql.CollectedField, obj *types.Audit) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Audit_controls(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.Audit().Controls(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.ControlOrderBy), fc.Args["filter"].(*types.ControlFilter)) + }) + 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.ControlConnection) + fc.Result = res + return ec.marshalNControlConnection2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐControlConnection(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Audit_controls(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Audit", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "totalCount": + return ec.fieldContext_ControlConnection_totalCount(ctx, field) + case "edges": + return ec.fieldContext_ControlConnection_edges(ctx, field) + case "pageInfo": + return ec.fieldContext_ControlConnection_pageInfo(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type ControlConnection", 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_Audit_controls_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Audit_showOnTrustCenter(ctx context.Context, field graphql.CollectedField, obj *types.Audit) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Audit_showOnTrustCenter(ctx, field) if err != nil { @@ -16699,6 +17157,8 @@ func (ec *executionContext) fieldContext_AuditEdge_node(_ context.Context, field return ec.fieldContext_Audit_reportUrl(ctx, field) case "state": return ec.fieldContext_Audit_state(ctx, field) + case "controls": + return ec.fieldContext_Audit_controls(ctx, field) case "showOnTrustCenter": return ec.fieldContext_Audit_showOnTrustCenter(ctx, field) case "createdAt": @@ -17821,6 +18281,69 @@ func (ec *executionContext) fieldContext_Control_documents(ctx context.Context, return fc, nil } +func (ec *executionContext) _Control_audits(ctx context.Context, field graphql.CollectedField, obj *types.Control) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Control_audits(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.Control().Audits(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.AuditOrderBy)) + }) + 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.AuditConnection) + fc.Result = res + return ec.marshalNAuditConnection2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐAuditConnection(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Control_audits(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Control", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "totalCount": + return ec.fieldContext_AuditConnection_totalCount(ctx, field) + case "edges": + return ec.fieldContext_AuditConnection_edges(ctx, field) + case "pageInfo": + return ec.fieldContext_AuditConnection_pageInfo(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type AuditConnection", 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_Control_audits_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Control_createdAt(ctx context.Context, field graphql.CollectedField, obj *types.Control) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Control_createdAt(ctx, field) if err != nil { @@ -18158,6 +18681,8 @@ func (ec *executionContext) fieldContext_ControlEdge_node(_ context.Context, fie return ec.fieldContext_Control_measures(ctx, field) case "documents": return ec.fieldContext_Control_documents(ctx, field) + case "audits": + return ec.fieldContext_Control_audits(ctx, field) case "createdAt": return ec.fieldContext_Control_createdAt(ctx, field) case "updatedAt": @@ -18269,6 +18794,106 @@ func (ec *executionContext) fieldContext_CreateAuditPayload_auditEdge(_ context. return fc, nil } +func (ec *executionContext) _CreateControlAuditMappingPayload_controlEdge(ctx context.Context, field graphql.CollectedField, obj *types.CreateControlAuditMappingPayload) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_CreateControlAuditMappingPayload_controlEdge(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.ControlEdge, 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.ControlEdge) + fc.Result = res + return ec.marshalNControlEdge2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐControlEdge(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_CreateControlAuditMappingPayload_controlEdge(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "CreateControlAuditMappingPayload", + 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_ControlEdge_cursor(ctx, field) + case "node": + return ec.fieldContext_ControlEdge_node(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type ControlEdge", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _CreateControlAuditMappingPayload_auditEdge(ctx context.Context, field graphql.CollectedField, obj *types.CreateControlAuditMappingPayload) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_CreateControlAuditMappingPayload_auditEdge(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.AuditEdge, 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.AuditEdge) + fc.Result = res + return ec.marshalNAuditEdge2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐAuditEdge(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_CreateControlAuditMappingPayload_auditEdge(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "CreateControlAuditMappingPayload", + 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_AuditEdge_cursor(ctx, field) + case "node": + return ec.fieldContext_AuditEdge_node(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type AuditEdge", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _CreateControlDocumentMappingPayload_controlEdge(ctx context.Context, field graphql.CollectedField, obj *types.CreateControlDocumentMappingPayload) (ret graphql.Marshaler) { fc, err := ec.fieldContext_CreateControlDocumentMappingPayload_controlEdge(ctx, field) if err != nil { @@ -20301,6 +20926,8 @@ func (ec *executionContext) fieldContext_DeleteAuditReportPayload_audit(_ contex return ec.fieldContext_Audit_reportUrl(ctx, field) case "state": return ec.fieldContext_Audit_state(ctx, field) + case "controls": + return ec.fieldContext_Audit_controls(ctx, field) case "showOnTrustCenter": return ec.fieldContext_Audit_showOnTrustCenter(ctx, field) case "createdAt": @@ -20314,6 +20941,94 @@ func (ec *executionContext) fieldContext_DeleteAuditReportPayload_audit(_ contex return fc, nil } +func (ec *executionContext) _DeleteControlAuditMappingPayload_deletedControlId(ctx context.Context, field graphql.CollectedField, obj *types.DeleteControlAuditMappingPayload) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_DeleteControlAuditMappingPayload_deletedControlId(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.DeletedControlID, 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.(gid.GID) + fc.Result = res + return ec.marshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_DeleteControlAuditMappingPayload_deletedControlId(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "DeleteControlAuditMappingPayload", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _DeleteControlAuditMappingPayload_deletedAuditId(ctx context.Context, field graphql.CollectedField, obj *types.DeleteControlAuditMappingPayload) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_DeleteControlAuditMappingPayload_deletedAuditId(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.DeletedAuditID, 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.(gid.GID) + fc.Result = res + return ec.marshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_DeleteControlAuditMappingPayload_deletedAuditId(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "DeleteControlAuditMappingPayload", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + func (ec *executionContext) _DeleteControlDocumentMappingPayload_deletedControlId(ctx context.Context, field graphql.CollectedField, obj *types.DeleteControlDocumentMappingPayload) (ret graphql.Marshaler) { fc, err := ec.fieldContext_DeleteControlDocumentMappingPayload_deletedControlId(ctx, field) if err != nil { @@ -28505,6 +29220,128 @@ func (ec *executionContext) fieldContext_Mutation_deleteControlDocumentMapping(c return fc, nil } +func (ec *executionContext) _Mutation_createControlAuditMapping(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_createControlAuditMapping(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().CreateControlAuditMapping(rctx, fc.Args["input"].(types.CreateControlAuditMappingInput)) + }) + 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.CreateControlAuditMappingPayload) + fc.Result = res + return ec.marshalNCreateControlAuditMappingPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐCreateControlAuditMappingPayload(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_createControlAuditMapping(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 "controlEdge": + return ec.fieldContext_CreateControlAuditMappingPayload_controlEdge(ctx, field) + case "auditEdge": + return ec.fieldContext_CreateControlAuditMappingPayload_auditEdge(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type CreateControlAuditMappingPayload", 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_createControlAuditMapping_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + +func (ec *executionContext) _Mutation_deleteControlAuditMapping(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_deleteControlAuditMapping(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().DeleteControlAuditMapping(rctx, fc.Args["input"].(types.DeleteControlAuditMappingInput)) + }) + 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.DeleteControlAuditMappingPayload) + fc.Result = res + return ec.marshalNDeleteControlAuditMappingPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteControlAuditMappingPayload(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_deleteControlAuditMapping(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 "deletedControlId": + return ec.fieldContext_DeleteControlAuditMappingPayload_deletedControlId(ctx, field) + case "deletedAuditId": + return ec.fieldContext_DeleteControlAuditMappingPayload_deletedAuditId(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type DeleteControlAuditMappingPayload", 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_deleteControlAuditMapping_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Mutation_createTask(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Mutation_createTask(ctx, field) if err != nil { @@ -38136,6 +38973,8 @@ func (ec *executionContext) fieldContext_UpdateAuditPayload_audit(_ context.Cont return ec.fieldContext_Audit_reportUrl(ctx, field) case "state": return ec.fieldContext_Audit_state(ctx, field) + case "controls": + return ec.fieldContext_Audit_controls(ctx, field) case "showOnTrustCenter": return ec.fieldContext_Audit_showOnTrustCenter(ctx, field) case "createdAt": @@ -38206,6 +39045,8 @@ func (ec *executionContext) fieldContext_UpdateControlPayload_control(_ context. return ec.fieldContext_Control_measures(ctx, field) case "documents": return ec.fieldContext_Control_documents(ctx, field) + case "audits": + return ec.fieldContext_Control_audits(ctx, field) case "createdAt": return ec.fieldContext_Control_createdAt(ctx, field) case "updatedAt": @@ -39262,6 +40103,8 @@ func (ec *executionContext) fieldContext_UploadAuditReportPayload_audit(_ contex return ec.fieldContext_Audit_reportUrl(ctx, field) case "state": return ec.fieldContext_Audit_state(ctx, field) + case "controls": + return ec.fieldContext_Audit_controls(ctx, field) case "showOnTrustCenter": return ec.fieldContext_Audit_showOnTrustCenter(ctx, field) case "createdAt": @@ -47210,6 +48053,40 @@ func (ec *executionContext) unmarshalInputCreateAuditInput(ctx context.Context, return it, nil } +func (ec *executionContext) unmarshalInputCreateControlAuditMappingInput(ctx context.Context, obj any) (types.CreateControlAuditMappingInput, error) { + var it types.CreateControlAuditMappingInput + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"controlId", "auditId"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "controlId": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("controlId")) + data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v) + if err != nil { + return it, err + } + it.ControlID = data + case "auditId": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("auditId")) + data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v) + if err != nil { + return it, err + } + it.AuditID = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputCreateControlDocumentMappingInput(ctx context.Context, obj any) (types.CreateControlDocumentMappingInput, error) { var it types.CreateControlDocumentMappingInput asMap := map[string]any{} @@ -48391,6 +49268,40 @@ func (ec *executionContext) unmarshalInputDeleteAuditReportInput(ctx context.Con return it, nil } +func (ec *executionContext) unmarshalInputDeleteControlAuditMappingInput(ctx context.Context, obj any) (types.DeleteControlAuditMappingInput, error) { + var it types.DeleteControlAuditMappingInput + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"controlId", "auditId"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "controlId": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("controlId")) + data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v) + if err != nil { + return it, err + } + it.ControlID = data + case "auditId": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("auditId")) + data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v) + if err != nil { + return it, err + } + it.AuditID = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputDeleteControlDocumentMappingInput(ctx context.Context, obj any) (types.DeleteControlDocumentMappingInput, error) { var it types.DeleteControlDocumentMappingInput asMap := map[string]any{} @@ -52268,6 +53179,42 @@ func (ec *executionContext) _Audit(ctx context.Context, sel ast.SelectionSet, ob if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } + case "controls": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Audit_controls(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) case "showOnTrustCenter": out.Values[i] = ec._Audit_showOnTrustCenter(ctx, field, obj) if out.Values[i] == graphql.Null { @@ -52883,6 +53830,42 @@ func (ec *executionContext) _Control(ctx context.Context, sel ast.SelectionSet, continue } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "audits": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Control_audits(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) case "createdAt": out.Values[i] = ec._Control_createdAt(ctx, field, obj) @@ -53119,6 +54102,50 @@ func (ec *executionContext) _CreateAuditPayload(ctx context.Context, sel ast.Sel return out } +var createControlAuditMappingPayloadImplementors = []string{"CreateControlAuditMappingPayload"} + +func (ec *executionContext) _CreateControlAuditMappingPayload(ctx context.Context, sel ast.SelectionSet, obj *types.CreateControlAuditMappingPayload) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, createControlAuditMappingPayloadImplementors) + + 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("CreateControlAuditMappingPayload") + case "controlEdge": + out.Values[i] = ec._CreateControlAuditMappingPayload_controlEdge(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "auditEdge": + out.Values[i] = ec._CreateControlAuditMappingPayload_auditEdge(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 createControlDocumentMappingPayloadImplementors = []string{"CreateControlDocumentMappingPayload"} func (ec *executionContext) _CreateControlDocumentMappingPayload(ctx context.Context, sel ast.SelectionSet, obj *types.CreateControlDocumentMappingPayload) graphql.Marshaler { @@ -54293,6 +55320,50 @@ func (ec *executionContext) _DeleteAuditReportPayload(ctx context.Context, sel a return out } +var deleteControlAuditMappingPayloadImplementors = []string{"DeleteControlAuditMappingPayload"} + +func (ec *executionContext) _DeleteControlAuditMappingPayload(ctx context.Context, sel ast.SelectionSet, obj *types.DeleteControlAuditMappingPayload) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, deleteControlAuditMappingPayloadImplementors) + + 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("DeleteControlAuditMappingPayload") + case "deletedControlId": + out.Values[i] = ec._DeleteControlAuditMappingPayload_deletedControlId(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "deletedAuditId": + out.Values[i] = ec._DeleteControlAuditMappingPayload_deletedAuditId(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 deleteControlDocumentMappingPayloadImplementors = []string{"DeleteControlDocumentMappingPayload"} func (ec *executionContext) _DeleteControlDocumentMappingPayload(ctx context.Context, sel ast.SelectionSet, obj *types.DeleteControlDocumentMappingPayload) graphql.Marshaler { @@ -57462,6 +58533,20 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { out.Invalids++ } + case "createControlAuditMapping": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_createControlAuditMapping(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "deleteControlAuditMapping": + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_deleteControlAuditMapping(ctx, field) + }) + if out.Values[i] == graphql.Null { + out.Invalids++ + } case "createTask": out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { return ec._Mutation_createTask(ctx, field) @@ -63989,6 +65074,25 @@ func (ec *executionContext) marshalNCreateAuditPayload2ᚖgithubᚗcomᚋgetprob return ec._CreateAuditPayload(ctx, sel, v) } +func (ec *executionContext) unmarshalNCreateControlAuditMappingInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐCreateControlAuditMappingInput(ctx context.Context, v any) (types.CreateControlAuditMappingInput, error) { + res, err := ec.unmarshalInputCreateControlAuditMappingInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNCreateControlAuditMappingPayload2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐCreateControlAuditMappingPayload(ctx context.Context, sel ast.SelectionSet, v types.CreateControlAuditMappingPayload) graphql.Marshaler { + return ec._CreateControlAuditMappingPayload(ctx, sel, &v) +} + +func (ec *executionContext) marshalNCreateControlAuditMappingPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐCreateControlAuditMappingPayload(ctx context.Context, sel ast.SelectionSet, v *types.CreateControlAuditMappingPayload) 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._CreateControlAuditMappingPayload(ctx, sel, v) +} + func (ec *executionContext) unmarshalNCreateControlDocumentMappingInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐCreateControlDocumentMappingInput(ctx context.Context, v any) (types.CreateControlDocumentMappingInput, error) { res, err := ec.unmarshalInputCreateControlDocumentMappingInput(ctx, v) return res, graphql.ErrorOnPath(ctx, err) @@ -64624,6 +65728,25 @@ func (ec *executionContext) marshalNDeleteAuditReportPayload2ᚖgithubᚗcomᚋg return ec._DeleteAuditReportPayload(ctx, sel, v) } +func (ec *executionContext) unmarshalNDeleteControlAuditMappingInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteControlAuditMappingInput(ctx context.Context, v any) (types.DeleteControlAuditMappingInput, error) { + res, err := ec.unmarshalInputDeleteControlAuditMappingInput(ctx, v) + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNDeleteControlAuditMappingPayload2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteControlAuditMappingPayload(ctx context.Context, sel ast.SelectionSet, v types.DeleteControlAuditMappingPayload) graphql.Marshaler { + return ec._DeleteControlAuditMappingPayload(ctx, sel, &v) +} + +func (ec *executionContext) marshalNDeleteControlAuditMappingPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteControlAuditMappingPayload(ctx context.Context, sel ast.SelectionSet, v *types.DeleteControlAuditMappingPayload) 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._DeleteControlAuditMappingPayload(ctx, sel, v) +} + func (ec *executionContext) unmarshalNDeleteControlDocumentMappingInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐDeleteControlDocumentMappingInput(ctx context.Context, v any) (types.DeleteControlDocumentMappingInput, error) { res, err := ec.unmarshalInputDeleteControlDocumentMappingInput(ctx, v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/pkg/server/api/console/v1/types/types.go b/pkg/server/api/console/v1/types/types.go index a09fdb5b8..f6b5b1700 100644 --- a/pkg/server/api/console/v1/types/types.go +++ b/pkg/server/api/console/v1/types/types.go @@ -66,6 +66,7 @@ type Audit struct { Report *Report `json:"report,omitempty"` ReportURL *string `json:"reportUrl,omitempty"` State coredata.AuditState `json:"state"` + Controls *ControlConnection `json:"controls"` ShowOnTrustCenter bool `json:"showOnTrustCenter"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` @@ -150,6 +151,7 @@ type Control struct { Framework *Framework `json:"framework"` Measures *MeasureConnection `json:"measures"` Documents *DocumentConnection `json:"documents"` + Audits *AuditConnection `json:"audits"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } @@ -194,6 +196,16 @@ type CreateAuditPayload struct { AuditEdge *AuditEdge `json:"auditEdge"` } +type CreateControlAuditMappingInput struct { + ControlID gid.GID `json:"controlId"` + AuditID gid.GID `json:"auditId"` +} + +type CreateControlAuditMappingPayload struct { + ControlEdge *ControlEdge `json:"controlEdge"` + AuditEdge *AuditEdge `json:"auditEdge"` +} + type CreateControlDocumentMappingInput struct { ControlID gid.GID `json:"controlId"` DocumentID gid.GID `json:"documentId"` @@ -473,6 +485,16 @@ type DeleteAuditReportPayload struct { Audit *Audit `json:"audit"` } +type DeleteControlAuditMappingInput struct { + ControlID gid.GID `json:"controlId"` + AuditID gid.GID `json:"auditId"` +} + +type DeleteControlAuditMappingPayload struct { + DeletedControlID gid.GID `json:"deletedControlId"` + DeletedAuditID gid.GID `json:"deletedAuditId"` +} + type DeleteControlDocumentMappingInput struct { ControlID gid.GID `json:"controlId"` DocumentID gid.GID `json:"documentId"` diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go index 9921a5938..1b74b6658 100644 --- a/pkg/server/api/console/v1/v1_resolver.go +++ b/pkg/server/api/console/v1/v1_resolver.go @@ -179,6 +179,36 @@ func (r *auditResolver) ReportURL(ctx context.Context, obj *types.Audit) (*strin return url, nil } +// Controls is the resolver for the controls field. +func (r *auditResolver) Controls(ctx context.Context, obj *types.Audit, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ControlOrderBy, filter *types.ControlFilter) (*types.ControlConnection, error) { + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.ControlOrderField]{ + Field: coredata.ControlOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.ControlOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + var controlFilter = coredata.NewControlFilter(nil) + if filter != nil { + controlFilter = coredata.NewControlFilter(filter.Query) + } + + page, err := prb.Controls.ListForAuditID(ctx, obj.ID, cursor, controlFilter) + if err != nil { + return nil, fmt.Errorf("cannot list audit controls: %w", err) + } + + return types.NewControlConnection(page, r, obj.ID, controlFilter), nil +} + // TotalCount is the resolver for the totalCount field. func (r *auditConnectionResolver) TotalCount(ctx context.Context, obj *types.AuditConnection) (int, error) { prb := r.ProboService(ctx, obj.ParentID.TenantID()) @@ -267,6 +297,31 @@ func (r *controlResolver) Documents(ctx context.Context, obj *types.Control, fir return types.NewDocumentConnection(page, r, obj.ID, documentFilter), nil } +// Audits is the resolver for the audits field. +func (r *controlResolver) Audits(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.AuditOrderBy) (*types.AuditConnection, error) { + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.AuditOrderField]{ + Field: coredata.AuditOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.AuditOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.Audits.ListForControlID(ctx, obj.ID, cursor) + if err != nil { + return nil, fmt.Errorf("cannot list control audits: %w", err) + } + + return types.NewAuditConnection(page, r, obj.ID), nil +} + // TotalCount is the resolver for the totalCount field. func (r *controlConnectionResolver) TotalCount(ctx context.Context, obj *types.ControlConnection) (int, error) { prb := r.ProboService(ctx, obj.ParentID.TenantID()) @@ -1598,6 +1653,36 @@ func (r *mutationResolver) DeleteControlDocumentMapping(ctx context.Context, inp }, nil } +// CreateControlAuditMapping is the resolver for the createControlAuditMapping field. +func (r *mutationResolver) CreateControlAuditMapping(ctx context.Context, input types.CreateControlAuditMappingInput) (*types.CreateControlAuditMappingPayload, error) { + prb := r.ProboService(ctx, input.AuditID.TenantID()) + + control, audit, err := prb.Controls.CreateAuditMapping(ctx, input.ControlID, input.AuditID) + if err != nil { + return nil, fmt.Errorf("cannot create control audit mapping: %w", err) + } + + return &types.CreateControlAuditMappingPayload{ + ControlEdge: types.NewControlEdge(control, coredata.ControlOrderFieldCreatedAt), + AuditEdge: types.NewAuditEdge(audit, coredata.AuditOrderFieldCreatedAt), + }, nil +} + +// DeleteControlAuditMapping is the resolver for the deleteControlAuditMapping field. +func (r *mutationResolver) DeleteControlAuditMapping(ctx context.Context, input types.DeleteControlAuditMappingInput) (*types.DeleteControlAuditMappingPayload, error) { + prb := r.ProboService(ctx, input.AuditID.TenantID()) + + control, audit, err := prb.Controls.DeleteAuditMapping(ctx, input.ControlID, input.AuditID) + if err != nil { + return nil, fmt.Errorf("cannot delete control audit mapping: %w", err) + } + + return &types.DeleteControlAuditMappingPayload{ + DeletedControlID: control.ID, + DeletedAuditID: audit.ID, + }, nil +} + // CreateTask is the resolver for the createTask field. func (r *mutationResolver) CreateTask(ctx context.Context, input types.CreateTaskInput) (*types.CreateTaskPayload, error) { prb := r.ProboService(ctx, input.MeasureID.TenantID())