Add contols audits

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-08-14 13:42:47 +02:00
parent 215e97d9f5
commit 55d488edac
22 changed files with 3762 additions and 21 deletions

View File

@@ -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<Params> = (p: {
variables: {
input: {
auditId: string;
} & Params;
connections: string[];
};
}) => void;
type Props<Params> = {
audits: (LinkedAuditsCardFragment$key & { id: string })[];
params: Params;
disabled?: boolean;
connectionId: string;
onAttach: Mutation<Params>;
onDetach: Mutation<Params>;
variant?: "card" | "table";
};
export function LinkedAuditsCard<Params>(props: Props<Params>) {
const { __ } = useTranslate();
const [limit, setLimit] = useState<number | null>(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 (
<Wrapper padded className="space-y-[10px]">
{variant === "card" && (
<div className="flex justify-between">
<div className="text-lg font-semibold">{__("Audits")}</div>
<LinkedAuditsDialog
disabled={props.disabled}
linkedAudits={props.audits}
onLink={onAttach}
onUnlink={onDetach}
>
<Button variant="tertiary" icon={IconPlusLarge}>
{__("Link audit")}
</Button>
</LinkedAuditsDialog>
</div>
)}
<Table className={clsx(variant === "card" && "bg-invert")}>
<Thead>
<Tr>
<Th>{__("Name")}</Th>
<Th>{__("State")}</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{audits.length === 0 && (
<Tr>
<Td colSpan={3} className="text-center text-txt-secondary">
{__("No audits linked")}
</Td>
</Tr>
)}
{audits.map((audit) => (
<AuditRow
key={audit.id}
audit={audit}
onClick={onDetach}
/>
))}
{variant === "table" && (
<LinkedAuditsDialog
disabled={props.disabled}
linkedAudits={props.audits}
onLink={onAttach}
onUnlink={onDetach}
>
<TrButton colspan={3} icon={IconPlusLarge}>
{__("Link audit")}
</TrButton>
</LinkedAuditsDialog>
)}
</Tbody>
</Table>
{showMoreButton && (
<Button
variant="tertiary"
onClick={() => setLimit(null)}
className="mt-3 mx-auto"
icon={IconChevronDown}
>
{sprintf(__("Show %s more"), props.audits.length - limit)}
</Button>
)}
</Wrapper>
);
}
function AuditRow(props: {
audit: LinkedAuditsCardFragment$key & { id: string };
onClick: (auditId: string) => void;
}) {
const audit = useFragment(linkedAuditFragment, props.audit);
const organizationId = useOrganizationId();
const { __ } = useTranslate();
return (
<Tr to={`/organizations/${organizationId}/audits/${audit.id}`}>
<Td>
<div className="flex flex-col">
<div className="font-medium">
{audit.framework?.name}
</div>
{audit.name && (
<div className="text-sm text-txt-secondary">
{audit.name}
</div>
)}
</div>
</Td>
<Td>
<Badge color={getAuditStateVariant(audit.state)}>
{audit.state.replace(/_/g, " ")}
</Badge>
</Td>
<Td noLink width={50} className="text-end">
<Button
variant="secondary"
onClick={() => props.onClick(audit.id)}
icon={IconTrashCan}
>
{__("Unlink")}
</Button>
</Td>
</Tr>
);
}

View File

@@ -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 (
<Dialog trigger={children} title={__("Link audits")}>
<DialogContent>
<Suspense fallback={<Spinner centered />}>
<LinkedAuditsDialogContent {...props} />
</Suspense>
</DialogContent>
<DialogFooter exitLabel={__("Close")} />
</Dialog>
);
}
function LinkedAuditsDialogContent(props: Omit<Props, "children">) {
const organizationId = useOrganizationId();
const query = useLazyLoadQuery<LinkedAuditsDialogQuery>(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 (
<>
<div className="flex items-center gap-2 sticky top-0 relative py-4 bg-linear-to-b from-50% from-level-2 to-level-2/0 px-6">
<Input
icon={IconMagnifyingGlass}
placeholder={__("Search audits...")}
onValueChange={setSearch}
/>
</div>
<div className="divide-y divide-border-low">
{filteredAudits.map((audit) => (
<AuditRow
key={audit.id}
audit={audit}
linkedAudits={linkedIds}
onLink={props.onLink}
onUnlink={props.onUnlink}
disabled={props.disabled}
/>
))}
{hasNext && (
<InfiniteScrollTrigger
loading={isLoadingNext}
onView={() => loadNext(20)}
/>
)}
</div>
</>
);
}
type Audit = NodeOf<LinkedAuditsDialogFragment$data["audits"]>;
type RowProps = {
audit: Audit;
linkedAudits: Set<string>;
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 (
<button
className="py-4 flex items-center gap-4 hover:bg-subtle cursor-pointer px-6 w-full h-[100px]"
onClick={() => onClick(props.audit.id)}
>
<div className="flex flex-col items-start gap-1">
<div className="font-medium">
{props.audit.framework?.name}
</div>
{props.audit.name && (
<div className="text-sm text-txt-secondary">
{props.audit.name}
</div>
)}
</div>
<Badge color={getAuditStateVariant(props.audit.state)}>
{props.audit.state.replace(/_/g, " ")}
</Badge>
<Button
disabled={props.disabled}
className="ml-auto"
variant={isLinked ? "secondary" : "primary"}
asChild
>
<span>
<IconComponent size={16} /> {isLinked ? __("Unlink") : __("Link")}
</span>
</Button>
</button>
);
}

View File

@@ -0,0 +1,104 @@
/**
* @generated SignedSource<<eb9ce92b73884420f07e71096665149f>>
* @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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -120,6 +120,16 @@ export const frameworkControlNodeQuery = graphql`
}
}
}
audits(first: 100)
@connection(key: "FrameworkGraphControl_audits") {
__id
edges {
node {
id
...LinkedAuditsCardFragment
}
}
}
}
}
}

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<95d2adf687d1cae6938863ba004db9a8>>
* @generated SignedSource<<dec38386ca0015f6c77121c2740fa406>>
* @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;

View File

@@ -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) {
<div className="flex justify-between items-start">
<div className="flex items-center gap-4">
<div className="text-2xl">{auditEntry.framework?.name}</div>
<div className="flex items-center gap-3">
<FrameworkLogo name={auditEntry.framework?.name || ""} />
<div className="text-2xl">{auditEntry.framework?.name}</div>
</div>
<Badge variant={getAuditStateVariant(auditEntry.state || "NOT_STARTED")}>
{getAuditStateLabel(__, auditEntry.state || "NOT_STARTED")}
</Badge>

View File

@@ -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}
/>
/>
<LinkedAuditsCard
variant="card"
audits={control.audits?.edges.map((edge) => edge.node) ?? []}
params={{ controlId: control.id }}
connectionId={control.audits?.__id!}
onAttach={attachAudit}
onDetach={detachAudit}
disabled={isAttachingAudit || isDetachingAudit}
/>
</div>
</div>
);

View File

@@ -0,0 +1,237 @@
/**
* @generated SignedSource<<c1f987833f73f19a66f044b28688f1dd>>
* @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<string>;
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;

View File

@@ -0,0 +1,133 @@
/**
* @generated SignedSource<<f3f6c7f7f6e09f53b111e39ce4176b1c>>
* @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<string>;
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;