Add controls snapshots
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
200
apps/console/src/components/snapshots/LinkedSnapshotsCard.tsx
Normal file
200
apps/console/src/components/snapshots/LinkedSnapshotsCard.tsx
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
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 { LinkedSnapshotsCardFragment$key } from "./__generated__/LinkedSnapshotsCardFragment.graphql";
|
||||||
|
import { useFragment } from "react-relay";
|
||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import { sprintf, getSnapshotTypeLabel, getSnapshotTypeUrlPath } from "@probo/helpers";
|
||||||
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
|
import { LinkedSnapshotsDialog } from "./LinkedSnapshotsDialog";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
const linkedSnapshotFragment = graphql`
|
||||||
|
fragment LinkedSnapshotsCardFragment on Snapshot {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
description
|
||||||
|
type
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Mutation<Params> = (p: {
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
snapshotId: string;
|
||||||
|
} & Params;
|
||||||
|
connections: string[];
|
||||||
|
};
|
||||||
|
}) => void;
|
||||||
|
|
||||||
|
type Props<Params> = {
|
||||||
|
snapshots: (LinkedSnapshotsCardFragment$key & { id: string })[];
|
||||||
|
params: Params;
|
||||||
|
disabled?: boolean;
|
||||||
|
connectionId: string;
|
||||||
|
onAttach: Mutation<Params>;
|
||||||
|
onDetach: Mutation<Params>;
|
||||||
|
variant?: "card" | "table";
|
||||||
|
};
|
||||||
|
|
||||||
|
export function LinkedSnapshotsCard<Params>(props: Props<Params>) {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const [limit, setLimit] = useState<number | null>(4);
|
||||||
|
const snapshots = useMemo(() => {
|
||||||
|
return limit ? props.snapshots.slice(0, limit) : props.snapshots;
|
||||||
|
}, [props.snapshots, limit]);
|
||||||
|
const showMoreButton = limit !== null && props.snapshots.length > limit;
|
||||||
|
const variant = props.variant ?? "table";
|
||||||
|
|
||||||
|
const onAttach = (snapshotId: string) => {
|
||||||
|
props.onAttach({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
snapshotId,
|
||||||
|
...props.params,
|
||||||
|
},
|
||||||
|
connections: [props.connectionId],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDetach = (snapshotId: string) => {
|
||||||
|
props.onDetach({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
snapshotId,
|
||||||
|
...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">{__("Snapshots")}</div>
|
||||||
|
<LinkedSnapshotsDialog
|
||||||
|
disabled={props.disabled}
|
||||||
|
linkedSnapshots={props.snapshots}
|
||||||
|
onLink={onAttach}
|
||||||
|
onUnlink={onDetach}
|
||||||
|
>
|
||||||
|
<Button variant="tertiary" icon={IconPlusLarge}>
|
||||||
|
{__("Link snapshot")}
|
||||||
|
</Button>
|
||||||
|
</LinkedSnapshotsDialog>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Table className={clsx(variant === "card" && "bg-invert")}>
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th>{__("Name")}</Th>
|
||||||
|
<Th>{__("Type")}</Th>
|
||||||
|
{variant === "table" && <Th>{__("Description")}</Th>}
|
||||||
|
<Th>{__("Created")}</Th>
|
||||||
|
<Th></Th>
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
<Tbody>
|
||||||
|
{snapshots.length === 0 && (
|
||||||
|
<Tr>
|
||||||
|
<Td colSpan={variant === "table" ? 5 : 3} className="text-center text-txt-secondary">
|
||||||
|
{__("No snapshots linked")}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
)}
|
||||||
|
{snapshots.map((snapshot) => (
|
||||||
|
<SnapshotRow
|
||||||
|
key={snapshot.id}
|
||||||
|
snapshot={snapshot}
|
||||||
|
onClick={onDetach}
|
||||||
|
variant={variant}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{variant === "table" && (
|
||||||
|
<LinkedSnapshotsDialog
|
||||||
|
disabled={props.disabled}
|
||||||
|
linkedSnapshots={props.snapshots}
|
||||||
|
onLink={onAttach}
|
||||||
|
onUnlink={onDetach}
|
||||||
|
>
|
||||||
|
<TrButton colspan={variant === "table" ? 5 : 3} icon={IconPlusLarge}>
|
||||||
|
{__("Link snapshot")}
|
||||||
|
</TrButton>
|
||||||
|
</LinkedSnapshotsDialog>
|
||||||
|
)}
|
||||||
|
</Tbody>
|
||||||
|
</Table>
|
||||||
|
{showMoreButton && (
|
||||||
|
<Button
|
||||||
|
variant="tertiary"
|
||||||
|
onClick={() => setLimit(null)}
|
||||||
|
className="mt-3 mx-auto"
|
||||||
|
icon={IconChevronDown}
|
||||||
|
>
|
||||||
|
{sprintf(__("Show %s more"), props.snapshots.length - limit)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Wrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SnapshotRow(props: {
|
||||||
|
snapshot: LinkedSnapshotsCardFragment$key & { id: string };
|
||||||
|
onClick: (snapshotId: string) => void;
|
||||||
|
variant: "card" | "table";
|
||||||
|
}) {
|
||||||
|
const snapshot = useFragment(linkedSnapshotFragment, props.snapshot);
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
|
const { __, dateFormat } = useTranslate();
|
||||||
|
|
||||||
|
const urlPath = getSnapshotTypeUrlPath(snapshot.type);
|
||||||
|
const snapshotUrl = `/organizations/${organizationId}/snapshots/${snapshot.id}${urlPath}`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tr to={snapshotUrl}>
|
||||||
|
<Td className="font-medium">{snapshot.name}</Td>
|
||||||
|
<Td>
|
||||||
|
<Badge variant="neutral">
|
||||||
|
{getSnapshotTypeLabel(__, snapshot.type)}
|
||||||
|
</Badge>
|
||||||
|
</Td>
|
||||||
|
{props.variant === "table" && (
|
||||||
|
<Td className="text-txt-secondary">
|
||||||
|
{snapshot.description || __("No description")}
|
||||||
|
</Td>
|
||||||
|
)}
|
||||||
|
<Td className="text-txt-tertiary">
|
||||||
|
{dateFormat(snapshot.createdAt, { year: "numeric", month: "short", day: "numeric" })}
|
||||||
|
</Td>
|
||||||
|
<Td noLink width={50} className="text-end">
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => props.onClick(snapshot.id)}
|
||||||
|
icon={IconTrashCan}
|
||||||
|
>
|
||||||
|
{__("Unlink")}
|
||||||
|
</Button>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
194
apps/console/src/components/snapshots/LinkedSnapshotsDialog.tsx
Normal file
194
apps/console/src/components/snapshots/LinkedSnapshotsDialog.tsx
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogFooter,
|
||||||
|
Badge,
|
||||||
|
InfiniteScrollTrigger,
|
||||||
|
Input,
|
||||||
|
Spinner,
|
||||||
|
IconMagnifyingGlass,
|
||||||
|
IconPlusLarge,
|
||||||
|
IconTrashCan,
|
||||||
|
} from "@probo/ui";
|
||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { getSnapshotTypeLabel } from "@probo/helpers";
|
||||||
|
import { Suspense, useMemo, useState, type ReactNode } from "react";
|
||||||
|
import { graphql } from "relay-runtime";
|
||||||
|
import { useLazyLoadQuery, usePaginationFragment } from "react-relay";
|
||||||
|
import type { LinkedSnapshotsDialogQuery } from "./__generated__/LinkedSnapshotsDialogQuery.graphql";
|
||||||
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
|
import type { NodeOf } from "/types";
|
||||||
|
import type {
|
||||||
|
LinkedSnapshotsDialogFragment$data,
|
||||||
|
LinkedSnapshotsDialogFragment$key,
|
||||||
|
} from "./__generated__/LinkedSnapshotsDialogFragment.graphql";
|
||||||
|
|
||||||
|
const snapshotsQuery = graphql`
|
||||||
|
query LinkedSnapshotsDialogQuery($organizationId: ID!) {
|
||||||
|
organization: node(id: $organizationId) {
|
||||||
|
id
|
||||||
|
... on Organization {
|
||||||
|
...LinkedSnapshotsDialogFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const snapshotsFragment = graphql`
|
||||||
|
fragment LinkedSnapshotsDialogFragment on Organization
|
||||||
|
@refetchable(queryName: "LinkedSnapshotsDialogQuery_fragment")
|
||||||
|
@argumentDefinitions(
|
||||||
|
first: { type: "Int", defaultValue: 20 }
|
||||||
|
order: { type: "SnapshotOrder", defaultValue: null }
|
||||||
|
after: { type: "CursorKey", defaultValue: null }
|
||||||
|
before: { type: "CursorKey", defaultValue: null }
|
||||||
|
last: { type: "Int", defaultValue: null }
|
||||||
|
) {
|
||||||
|
snapshots(
|
||||||
|
first: $first
|
||||||
|
after: $after
|
||||||
|
last: $last
|
||||||
|
before: $before
|
||||||
|
orderBy: $order
|
||||||
|
) @connection(key: "LinkedSnapshotsDialogQuery_snapshots") {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
description
|
||||||
|
type
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
children: ReactNode;
|
||||||
|
disabled?: boolean;
|
||||||
|
linkedSnapshots?: { id: string }[];
|
||||||
|
onLink: (snapshotId: string) => void;
|
||||||
|
onUnlink: (snapshotId: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function LinkedSnapshotsDialog({ children, ...props }: Props) {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog trigger={children} title={__("Link snapshots")}>
|
||||||
|
<DialogContent>
|
||||||
|
<Suspense fallback={<Spinner centered />}>
|
||||||
|
<LinkedSnapshotsDialogContent {...props} />
|
||||||
|
</Suspense>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogFooter exitLabel={__("Close")} />
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function LinkedSnapshotsDialogContent(props: Omit<Props, "children">) {
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
|
const query = useLazyLoadQuery<LinkedSnapshotsDialogQuery>(snapshotsQuery, {
|
||||||
|
organizationId,
|
||||||
|
});
|
||||||
|
const { data, loadNext, hasNext, isLoadingNext } = usePaginationFragment<
|
||||||
|
LinkedSnapshotsDialogQuery,
|
||||||
|
LinkedSnapshotsDialogFragment$key>(
|
||||||
|
snapshotsFragment,
|
||||||
|
query.organization
|
||||||
|
);
|
||||||
|
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const snapshots = data.snapshots?.edges?.map((edge) => edge.node) ?? [];
|
||||||
|
const linkedIds = useMemo(() => {
|
||||||
|
return new Set(props.linkedSnapshots?.map((s) => s.id) ?? []);
|
||||||
|
}, [props.linkedSnapshots]);
|
||||||
|
|
||||||
|
const filteredSnapshots = useMemo(() => {
|
||||||
|
return snapshots.filter((snapshot) =>
|
||||||
|
snapshot.name.toLowerCase().includes(search.toLowerCase())
|
||||||
|
);
|
||||||
|
}, [snapshots, 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 snapshots...")}
|
||||||
|
onValueChange={setSearch}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="divide-y divide-border-low">
|
||||||
|
{filteredSnapshots.map((snapshot) => (
|
||||||
|
<SnapshotRow
|
||||||
|
key={snapshot.id}
|
||||||
|
snapshot={snapshot}
|
||||||
|
linkedSnapshots={linkedIds}
|
||||||
|
onLink={props.onLink}
|
||||||
|
onUnlink={props.onUnlink}
|
||||||
|
disabled={props.disabled}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{hasNext && (
|
||||||
|
<InfiniteScrollTrigger
|
||||||
|
loading={isLoadingNext}
|
||||||
|
onView={() => loadNext(20)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type Snapshot = NodeOf<LinkedSnapshotsDialogFragment$data["snapshots"]>;
|
||||||
|
|
||||||
|
type RowProps = {
|
||||||
|
snapshot: Snapshot;
|
||||||
|
linkedSnapshots: Set<string>;
|
||||||
|
disabled?: boolean;
|
||||||
|
onLink: (snapshotId: string) => void;
|
||||||
|
onUnlink: (snapshotId: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
function SnapshotRow(props: RowProps) {
|
||||||
|
const { __, dateFormat } = useTranslate();
|
||||||
|
|
||||||
|
const isLinked = props.linkedSnapshots.has(props.snapshot.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"
|
||||||
|
onClick={() => onClick(props.snapshot.id)}
|
||||||
|
>
|
||||||
|
<div className="flex-1 flex items-center gap-4">
|
||||||
|
<div className="font-medium min-w-0 flex-shrink-0">
|
||||||
|
{props.snapshot.name}
|
||||||
|
</div>
|
||||||
|
<Badge variant="neutral" className="flex-shrink-0 ml-6">
|
||||||
|
{getSnapshotTypeLabel(__, props.snapshot.type)}
|
||||||
|
</Badge>
|
||||||
|
<div className="text-sm text-txt-secondary min-w-0 flex-1 text-left">
|
||||||
|
{props.snapshot.description || __("No description")}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-txt-tertiary flex-shrink-0">
|
||||||
|
{dateFormat(props.snapshot.createdAt, { year: "numeric", month: "short", day: "numeric" })}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
disabled={props.disabled}
|
||||||
|
variant={isLinked ? "secondary" : "primary"}
|
||||||
|
asChild
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
<IconComponent size={16} /> {isLinked ? __("Unlink") : __("Link")}
|
||||||
|
</span>
|
||||||
|
</Button>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
75
apps/console/src/components/snapshots/__generated__/LinkedSnapshotsCardFragment.graphql.ts
generated
Normal file
75
apps/console/src/components/snapshots/__generated__/LinkedSnapshotsCardFragment.graphql.ts
generated
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<e9fde8de02cc2e3ec97ce64b67818bfe>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ReaderFragment } from 'relay-runtime';
|
||||||
|
export type SnapshotsType = "ASSETS" | "COMPLIANCE_REGISTRIES" | "DATA" | "NON_CONFORMITY_REGISTRIES" | "RISKS" | "VENDORS";
|
||||||
|
import { FragmentRefs } from "relay-runtime";
|
||||||
|
export type LinkedSnapshotsCardFragment$data = {
|
||||||
|
readonly createdAt: any;
|
||||||
|
readonly description: string | null | undefined;
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly type: SnapshotsType;
|
||||||
|
readonly " $fragmentType": "LinkedSnapshotsCardFragment";
|
||||||
|
};
|
||||||
|
export type LinkedSnapshotsCardFragment$key = {
|
||||||
|
readonly " $data"?: LinkedSnapshotsCardFragment$data;
|
||||||
|
readonly " $fragmentSpreads": FragmentRefs<"LinkedSnapshotsCardFragment">;
|
||||||
|
};
|
||||||
|
|
||||||
|
const node: ReaderFragment = {
|
||||||
|
"argumentDefinitions": [],
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "LinkedSnapshotsCardFragment",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "description",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "type",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "createdAt",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Snapshot",
|
||||||
|
"abstractKey": null
|
||||||
|
};
|
||||||
|
|
||||||
|
(node as any).hash = "b9b682f8e57082c98d8b0460155757fb";
|
||||||
|
|
||||||
|
export default node;
|
||||||
239
apps/console/src/components/snapshots/__generated__/LinkedSnapshotsDialogFragment.graphql.ts
generated
Normal file
239
apps/console/src/components/snapshots/__generated__/LinkedSnapshotsDialogFragment.graphql.ts
generated
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<b68d2b7e020579736ca058b633352dee>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ReaderFragment } from 'relay-runtime';
|
||||||
|
export type SnapshotsType = "ASSETS" | "COMPLIANCE_REGISTRIES" | "DATA" | "NON_CONFORMITY_REGISTRIES" | "RISKS" | "VENDORS";
|
||||||
|
import { FragmentRefs } from "relay-runtime";
|
||||||
|
export type LinkedSnapshotsDialogFragment$data = {
|
||||||
|
readonly id: string;
|
||||||
|
readonly snapshots: {
|
||||||
|
readonly edges: ReadonlyArray<{
|
||||||
|
readonly node: {
|
||||||
|
readonly createdAt: any;
|
||||||
|
readonly description: string | null | undefined;
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly type: SnapshotsType;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
readonly " $fragmentType": "LinkedSnapshotsDialogFragment";
|
||||||
|
};
|
||||||
|
export type LinkedSnapshotsDialogFragment$key = {
|
||||||
|
readonly " $data"?: LinkedSnapshotsDialogFragment$data;
|
||||||
|
readonly " $fragmentSpreads": FragmentRefs<"LinkedSnapshotsDialogFragment">;
|
||||||
|
};
|
||||||
|
|
||||||
|
import LinkedSnapshotsDialogQuery_fragment_graphql from './LinkedSnapshotsDialogQuery_fragment.graphql';
|
||||||
|
|
||||||
|
const node: ReaderFragment = (function(){
|
||||||
|
var v0 = [
|
||||||
|
"snapshots"
|
||||||
|
],
|
||||||
|
v1 = {
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "id",
|
||||||
|
"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": LinkedSnapshotsDialogQuery_fragment_graphql,
|
||||||
|
"identifierInfo": {
|
||||||
|
"identifierField": "id",
|
||||||
|
"identifierQueryVariableName": "id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "LinkedSnapshotsDialogFragment",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": "snapshots",
|
||||||
|
"args": [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "orderBy",
|
||||||
|
"variableName": "order"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"concreteType": "SnapshotConnection",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "__LinkedSnapshotsDialogQuery_snapshots_connection",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "SnapshotEdge",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "edges",
|
||||||
|
"plural": true,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Snapshot",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v1/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "description",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "type",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "createdAt",
|
||||||
|
"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 = "bdc77ee756ac59c099caa8765403cd22";
|
||||||
|
|
||||||
|
export default node;
|
||||||
259
apps/console/src/components/snapshots/__generated__/LinkedSnapshotsDialogQuery.graphql.ts
generated
Normal file
259
apps/console/src/components/snapshots/__generated__/LinkedSnapshotsDialogQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,259 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<78d7f0f48f20187e9eef7744a19d8e1e>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
import { FragmentRefs } from "relay-runtime";
|
||||||
|
export type LinkedSnapshotsDialogQuery$variables = {
|
||||||
|
organizationId: string;
|
||||||
|
};
|
||||||
|
export type LinkedSnapshotsDialogQuery$data = {
|
||||||
|
readonly organization: {
|
||||||
|
readonly id: string;
|
||||||
|
readonly " $fragmentSpreads": FragmentRefs<"LinkedSnapshotsDialogFragment">;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type LinkedSnapshotsDialogQuery = {
|
||||||
|
response: LinkedSnapshotsDialogQuery$data;
|
||||||
|
variables: LinkedSnapshotsDialogQuery$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
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "LinkedSnapshotsDialogQuery",
|
||||||
|
"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": "LinkedSnapshotsDialogFragment"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Organization",
|
||||||
|
"abstractKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Query",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "LinkedSnapshotsDialogQuery",
|
||||||
|
"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": "SnapshotConnection",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "snapshots",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "SnapshotEdge",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "edges",
|
||||||
|
"plural": true,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Snapshot",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v2/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "description",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "type",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "createdAt",
|
||||||
|
"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": "snapshots(first:20)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v4/*: any*/),
|
||||||
|
"filters": [
|
||||||
|
"orderBy"
|
||||||
|
],
|
||||||
|
"handle": "connection",
|
||||||
|
"key": "LinkedSnapshotsDialogQuery_snapshots",
|
||||||
|
"kind": "LinkedHandle",
|
||||||
|
"name": "snapshots"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Organization",
|
||||||
|
"abstractKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "200a82b795d348f0d8a63a3b2b779d7c",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "LinkedSnapshotsDialogQuery",
|
||||||
|
"operationKind": "query",
|
||||||
|
"text": "query LinkedSnapshotsDialogQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n ...LinkedSnapshotsDialogFragment\n }\n }\n}\n\nfragment LinkedSnapshotsDialogFragment on Organization {\n snapshots(first: 20) {\n edges {\n node {\n id\n name\n description\n type\n createdAt\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 = "b91ff32883e1f6e83d9b43251fc83001";
|
||||||
|
|
||||||
|
export default node;
|
||||||
332
apps/console/src/components/snapshots/__generated__/LinkedSnapshotsDialogQuery_fragment.graphql.ts
generated
Normal file
332
apps/console/src/components/snapshots/__generated__/LinkedSnapshotsDialogQuery_fragment.graphql.ts
generated
Normal file
@@ -0,0 +1,332 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<da66b640f5bf710bb086e806a82b03e6>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
import { FragmentRefs } from "relay-runtime";
|
||||||
|
export type OrderDirection = "ASC" | "DESC";
|
||||||
|
export type SnapshotOrderField = "CREATED_AT" | "NAME" | "TYPE";
|
||||||
|
export type SnapshotOrder = {
|
||||||
|
direction: OrderDirection;
|
||||||
|
field: SnapshotOrderField;
|
||||||
|
};
|
||||||
|
export type LinkedSnapshotsDialogQuery_fragment$variables = {
|
||||||
|
after?: any | null | undefined;
|
||||||
|
before?: any | null | undefined;
|
||||||
|
first?: number | null | undefined;
|
||||||
|
id: string;
|
||||||
|
last?: number | null | undefined;
|
||||||
|
order?: SnapshotOrder | null | undefined;
|
||||||
|
};
|
||||||
|
export type LinkedSnapshotsDialogQuery_fragment$data = {
|
||||||
|
readonly node: {
|
||||||
|
readonly " $fragmentSpreads": FragmentRefs<"LinkedSnapshotsDialogFragment">;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type LinkedSnapshotsDialogQuery_fragment = {
|
||||||
|
response: LinkedSnapshotsDialogQuery_fragment$data;
|
||||||
|
variables: LinkedSnapshotsDialogQuery_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"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": [
|
||||||
|
(v0/*: any*/),
|
||||||
|
(v1/*: any*/),
|
||||||
|
(v2/*: any*/),
|
||||||
|
(v3/*: any*/),
|
||||||
|
(v4/*: any*/),
|
||||||
|
(v5/*: any*/)
|
||||||
|
],
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "LinkedSnapshotsDialogQuery_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": "LinkedSnapshotsDialogFragment"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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": "LinkedSnapshotsDialogQuery_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": "SnapshotConnection",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "snapshots",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "SnapshotEdge",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "edges",
|
||||||
|
"plural": true,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Snapshot",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v12/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "description",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "type",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "createdAt",
|
||||||
|
"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": "LinkedSnapshotsDialogQuery_snapshots",
|
||||||
|
"kind": "LinkedHandle",
|
||||||
|
"name": "snapshots"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Organization",
|
||||||
|
"abstractKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "e4c40c765a5b6ef4ba63a6343c76e6e0",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "LinkedSnapshotsDialogQuery_fragment",
|
||||||
|
"operationKind": "query",
|
||||||
|
"text": "query LinkedSnapshotsDialogQuery_fragment(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: SnapshotOrder = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...LinkedSnapshotsDialogFragment_16fISc\n id\n }\n}\n\nfragment LinkedSnapshotsDialogFragment_16fISc on Organization {\n snapshots(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n edges {\n node {\n id\n name\n description\n type\n createdAt\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 = "bdc77ee756ac59c099caa8765403cd22";
|
||||||
|
|
||||||
|
export default node;
|
||||||
@@ -130,6 +130,16 @@ export const frameworkControlNodeQuery = graphql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
snapshots(first: 100)
|
||||||
|
@connection(key: "FrameworkGraphControl_snapshots") {
|
||||||
|
__id
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
...LinkedSnapshotsCardFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<dec38386ca0015f6c77121c2740fa406>>
|
* @generated SignedSource<<0e6281a892cbd711d75d22153d4710ea>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -48,6 +48,15 @@ export type FrameworkGraphControlNodeQuery$data = {
|
|||||||
};
|
};
|
||||||
readonly name?: string;
|
readonly name?: string;
|
||||||
readonly sectionTitle?: string;
|
readonly sectionTitle?: string;
|
||||||
|
readonly snapshots?: {
|
||||||
|
readonly __id: string;
|
||||||
|
readonly edges: ReadonlyArray<{
|
||||||
|
readonly node: {
|
||||||
|
readonly id: string;
|
||||||
|
readonly " $fragmentSpreads": FragmentRefs<"LinkedSnapshotsCardFragment">;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
};
|
||||||
readonly status?: ControlStatus;
|
readonly status?: ControlStatus;
|
||||||
readonly " $fragmentSpreads": FragmentRefs<"FrameworkControlDialogFragment">;
|
readonly " $fragmentSpreads": FragmentRefs<"FrameworkControlDialogFragment">;
|
||||||
};
|
};
|
||||||
@@ -343,6 +352,49 @@ return {
|
|||||||
(v11/*: any*/)
|
(v11/*: any*/)
|
||||||
],
|
],
|
||||||
"storageKey": null
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": "snapshots",
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "SnapshotConnection",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "__FrameworkGraphControl_snapshots_connection",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "SnapshotEdge",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "edges",
|
||||||
|
"plural": true,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Snapshot",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v2/*: any*/),
|
||||||
|
{
|
||||||
|
"args": null,
|
||||||
|
"kind": "FragmentSpread",
|
||||||
|
"name": "LinkedSnapshotsCardFragment"
|
||||||
|
},
|
||||||
|
(v8/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
(v9/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
(v10/*: any*/),
|
||||||
|
(v11/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": "Control",
|
"type": "Control",
|
||||||
@@ -607,6 +659,63 @@ return {
|
|||||||
"key": "FrameworkGraphControl_audits",
|
"key": "FrameworkGraphControl_audits",
|
||||||
"kind": "LinkedHandle",
|
"kind": "LinkedHandle",
|
||||||
"name": "audits"
|
"name": "audits"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v12/*: any*/),
|
||||||
|
"concreteType": "SnapshotConnection",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "snapshots",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "SnapshotEdge",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "edges",
|
||||||
|
"plural": true,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Snapshot",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v2/*: any*/),
|
||||||
|
(v3/*: any*/),
|
||||||
|
(v5/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "type",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
(v14/*: any*/),
|
||||||
|
(v8/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
(v9/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
(v10/*: any*/),
|
||||||
|
(v11/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": "snapshots(first:100)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v12/*: any*/),
|
||||||
|
"filters": null,
|
||||||
|
"handle": "connection",
|
||||||
|
"key": "FrameworkGraphControl_snapshots",
|
||||||
|
"kind": "LinkedHandle",
|
||||||
|
"name": "snapshots"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": "Control",
|
"type": "Control",
|
||||||
@@ -618,7 +727,7 @@ return {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "20a0b80a6b0d796951c7f8c6eca41d3c",
|
"cacheID": "2ce9eb2cbe052019e86b2d0baecfb6f0",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"connection": [
|
"connection": [
|
||||||
@@ -648,16 +757,25 @@ return {
|
|||||||
"node",
|
"node",
|
||||||
"audits"
|
"audits"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"count": null,
|
||||||
|
"cursor": null,
|
||||||
|
"direction": "forward",
|
||||||
|
"path": [
|
||||||
|
"node",
|
||||||
|
"snapshots"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"name": "FrameworkGraphControlNodeQuery",
|
"name": "FrameworkGraphControlNodeQuery",
|
||||||
"operationKind": "query",
|
"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 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"
|
"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 snapshots(first: 100) {\n edges {\n node {\n id\n ...LinkedSnapshotsCardFragment\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\nfragment LinkedSnapshotsCardFragment on Snapshot {\n id\n name\n description\n type\n createdAt\n}\n"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "ac5240126dfbf4882ba2b111e865045d";
|
(node as any).hash = "afc4bbbce8d8b3cd57ac2bf77db58e55";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { useNavigate, useOutletContext } from "react-router";
|
|||||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
import { LinkedDocumentsCard } from "/components/documents/LinkedDocumentsCard";
|
import { LinkedDocumentsCard } from "/components/documents/LinkedDocumentsCard";
|
||||||
import { LinkedAuditsCard } from "/components/audits/LinkedAuditsCard";
|
import { LinkedAuditsCard } from "/components/audits/LinkedAuditsCard";
|
||||||
|
import { LinkedSnapshotsCard } from "/components/snapshots/LinkedSnapshotsCard";
|
||||||
import { FrameworkControlDialog } from "./dialogs/FrameworkControlDialog";
|
import { FrameworkControlDialog } from "./dialogs/FrameworkControlDialog";
|
||||||
import { promisifyMutation } from "@probo/helpers";
|
import { promisifyMutation } from "@probo/helpers";
|
||||||
import type { FrameworkGraphControlNodeQuery } from "/hooks/graph/__generated__/FrameworkGraphControlNodeQuery.graphql";
|
import type { FrameworkGraphControlNodeQuery } from "/hooks/graph/__generated__/FrameworkGraphControlNodeQuery.graphql";
|
||||||
@@ -105,6 +106,33 @@ const detachAuditMutation = graphql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const attachSnapshotMutation = graphql`
|
||||||
|
mutation FrameworkControlPageAttachSnapshotMutation(
|
||||||
|
$input: CreateControlSnapshotMappingInput!
|
||||||
|
$connections: [ID!]!
|
||||||
|
) {
|
||||||
|
createControlSnapshotMapping(input: $input) {
|
||||||
|
snapshotEdge @prependEdge(connections: $connections) {
|
||||||
|
node {
|
||||||
|
id
|
||||||
|
...LinkedSnapshotsCardFragment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const detachSnapshotMutation = graphql`
|
||||||
|
mutation FrameworkControlPageDetachSnapshotMutation(
|
||||||
|
$input: DeleteControlSnapshotMappingInput!
|
||||||
|
$connections: [ID!]!
|
||||||
|
) {
|
||||||
|
deleteControlSnapshotMapping(input: $input) {
|
||||||
|
deletedSnapshotId @deleteEdge(connections: $connections)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
const deleteControlMutation = graphql`
|
const deleteControlMutation = graphql`
|
||||||
mutation FrameworkControlPageDeleteControlMutation(
|
mutation FrameworkControlPageDeleteControlMutation(
|
||||||
$input: DeleteControlInput!
|
$input: DeleteControlInput!
|
||||||
@@ -148,6 +176,8 @@ export default function FrameworkControlPage({ queryRef }: Props) {
|
|||||||
);
|
);
|
||||||
const [detachAudit, isDetachingAudit] = useMutation(detachAuditMutation);
|
const [detachAudit, isDetachingAudit] = useMutation(detachAuditMutation);
|
||||||
const [attachAudit, isAttachingAudit] = useMutation(attachAuditMutation);
|
const [attachAudit, isAttachingAudit] = useMutation(attachAuditMutation);
|
||||||
|
const [detachSnapshot, isDetachingSnapshot] = useMutation(detachSnapshotMutation);
|
||||||
|
const [attachSnapshot, isAttachingSnapshot] = useMutation(attachSnapshotMutation);
|
||||||
const [deleteControl] = useMutation(deleteControlMutation);
|
const [deleteControl] = useMutation(deleteControlMutation);
|
||||||
|
|
||||||
const onDelete = () => {
|
const onDelete = () => {
|
||||||
@@ -216,7 +246,8 @@ export default function FrameworkControlPage({ queryRef }: Props) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className={control.status === "EXCLUDED" ? "opacity-60" : ""}>
|
<div className={control.status === "EXCLUDED" ? "opacity-60" : ""}>
|
||||||
<div className="text-base">{control.name}</div>
|
<div className="text-base mb-4">{control.name}</div>
|
||||||
|
<div className="mb-4">
|
||||||
<LinkedMeasuresCard
|
<LinkedMeasuresCard
|
||||||
variant="card"
|
variant="card"
|
||||||
measures={control.measures?.edges.map((edge) => edge.node) ?? []}
|
measures={control.measures?.edges.map((edge) => edge.node) ?? []}
|
||||||
@@ -226,6 +257,8 @@ export default function FrameworkControlPage({ queryRef }: Props) {
|
|||||||
onDetach={detachMeasure}
|
onDetach={detachMeasure}
|
||||||
disabled={isAttachingMeasure || isDetachingMeasure}
|
disabled={isAttachingMeasure || isDetachingMeasure}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
<LinkedDocumentsCard
|
<LinkedDocumentsCard
|
||||||
variant="card"
|
variant="card"
|
||||||
documents={control.documents?.edges.map((edge) => edge.node) ?? []}
|
documents={control.documents?.edges.map((edge) => edge.node) ?? []}
|
||||||
@@ -235,6 +268,8 @@ export default function FrameworkControlPage({ queryRef }: Props) {
|
|||||||
onDetach={detachDocument}
|
onDetach={detachDocument}
|
||||||
disabled={isAttachingDocument || isDetachingDocument}
|
disabled={isAttachingDocument || isDetachingDocument}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
<LinkedAuditsCard
|
<LinkedAuditsCard
|
||||||
variant="card"
|
variant="card"
|
||||||
audits={control.audits?.edges.map((edge) => edge.node) ?? []}
|
audits={control.audits?.edges.map((edge) => edge.node) ?? []}
|
||||||
@@ -245,6 +280,18 @@ export default function FrameworkControlPage({ queryRef }: Props) {
|
|||||||
disabled={isAttachingAudit || isDetachingAudit}
|
disabled={isAttachingAudit || isDetachingAudit}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<LinkedSnapshotsCard
|
||||||
|
variant="card"
|
||||||
|
snapshots={control.snapshots?.edges.map((edge) => edge.node) ?? []}
|
||||||
|
params={{ controlId: control.id }}
|
||||||
|
connectionId={control.snapshots?.__id!}
|
||||||
|
onAttach={attachSnapshot}
|
||||||
|
onDetach={detachSnapshot}
|
||||||
|
disabled={isAttachingSnapshot || isDetachingSnapshot}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,216 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<a4b20010a96dac4779e4b1576b947e45>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
import { FragmentRefs } from "relay-runtime";
|
||||||
|
export type CreateControlSnapshotMappingInput = {
|
||||||
|
controlId: string;
|
||||||
|
snapshotId: string;
|
||||||
|
};
|
||||||
|
export type FrameworkControlPageAttachSnapshotMutation$variables = {
|
||||||
|
connections: ReadonlyArray<string>;
|
||||||
|
input: CreateControlSnapshotMappingInput;
|
||||||
|
};
|
||||||
|
export type FrameworkControlPageAttachSnapshotMutation$data = {
|
||||||
|
readonly createControlSnapshotMapping: {
|
||||||
|
readonly snapshotEdge: {
|
||||||
|
readonly node: {
|
||||||
|
readonly id: string;
|
||||||
|
readonly " $fragmentSpreads": FragmentRefs<"LinkedSnapshotsCardFragment">;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type FrameworkControlPageAttachSnapshotMutation = {
|
||||||
|
response: FrameworkControlPageAttachSnapshotMutation$data;
|
||||||
|
variables: FrameworkControlPageAttachSnapshotMutation$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
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": [
|
||||||
|
(v0/*: any*/),
|
||||||
|
(v1/*: any*/)
|
||||||
|
],
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "FrameworkControlPageAttachSnapshotMutation",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v2/*: any*/),
|
||||||
|
"concreteType": "CreateControlSnapshotMappingPayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "createControlSnapshotMapping",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "SnapshotEdge",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "snapshotEdge",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Snapshot",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v3/*: any*/),
|
||||||
|
{
|
||||||
|
"args": null,
|
||||||
|
"kind": "FragmentSpread",
|
||||||
|
"name": "LinkedSnapshotsCardFragment"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Mutation",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": [
|
||||||
|
(v1/*: any*/),
|
||||||
|
(v0/*: any*/)
|
||||||
|
],
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "FrameworkControlPageAttachSnapshotMutation",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v2/*: any*/),
|
||||||
|
"concreteType": "CreateControlSnapshotMappingPayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "createControlSnapshotMapping",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "SnapshotEdge",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "snapshotEdge",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"concreteType": "Snapshot",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "node",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v3/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "name",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "description",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "type",
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"kind": "ScalarField",
|
||||||
|
"name": "createdAt",
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"filters": null,
|
||||||
|
"handle": "prependEdge",
|
||||||
|
"key": "",
|
||||||
|
"kind": "LinkedHandle",
|
||||||
|
"name": "snapshotEdge",
|
||||||
|
"handleArgs": [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "connections",
|
||||||
|
"variableName": "connections"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "52c76e336bb7f38dc2d2f6620817100a",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "FrameworkControlPageAttachSnapshotMutation",
|
||||||
|
"operationKind": "mutation",
|
||||||
|
"text": "mutation FrameworkControlPageAttachSnapshotMutation(\n $input: CreateControlSnapshotMappingInput!\n) {\n createControlSnapshotMapping(input: $input) {\n snapshotEdge {\n node {\n id\n ...LinkedSnapshotsCardFragment\n }\n }\n }\n}\n\nfragment LinkedSnapshotsCardFragment on Snapshot {\n id\n name\n description\n type\n createdAt\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "9e8ad62cc8d9f3672e1f785c31a91624";
|
||||||
|
|
||||||
|
export default node;
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
/**
|
||||||
|
* @generated SignedSource<<d4c60256aa53236cfb4c3a01f4fd35c1>>
|
||||||
|
* @lightSyntaxTransform
|
||||||
|
* @nogrep
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* tslint:disable */
|
||||||
|
/* eslint-disable */
|
||||||
|
// @ts-nocheck
|
||||||
|
|
||||||
|
import { ConcreteRequest } from 'relay-runtime';
|
||||||
|
export type DeleteControlSnapshotMappingInput = {
|
||||||
|
controlId: string;
|
||||||
|
snapshotId: string;
|
||||||
|
};
|
||||||
|
export type FrameworkControlPageDetachSnapshotMutation$variables = {
|
||||||
|
connections: ReadonlyArray<string>;
|
||||||
|
input: DeleteControlSnapshotMappingInput;
|
||||||
|
};
|
||||||
|
export type FrameworkControlPageDetachSnapshotMutation$data = {
|
||||||
|
readonly deleteControlSnapshotMapping: {
|
||||||
|
readonly deletedSnapshotId: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export type FrameworkControlPageDetachSnapshotMutation = {
|
||||||
|
response: FrameworkControlPageDetachSnapshotMutation$data;
|
||||||
|
variables: FrameworkControlPageDetachSnapshotMutation$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": "deletedSnapshotId",
|
||||||
|
"storageKey": null
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
"fragment": {
|
||||||
|
"argumentDefinitions": [
|
||||||
|
(v0/*: any*/),
|
||||||
|
(v1/*: any*/)
|
||||||
|
],
|
||||||
|
"kind": "Fragment",
|
||||||
|
"metadata": null,
|
||||||
|
"name": "FrameworkControlPageDetachSnapshotMutation",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v2/*: any*/),
|
||||||
|
"concreteType": "DeleteControlSnapshotMappingPayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "deleteControlSnapshotMapping",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v3/*: any*/)
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "Mutation",
|
||||||
|
"abstractKey": null
|
||||||
|
},
|
||||||
|
"kind": "Request",
|
||||||
|
"operation": {
|
||||||
|
"argumentDefinitions": [
|
||||||
|
(v1/*: any*/),
|
||||||
|
(v0/*: any*/)
|
||||||
|
],
|
||||||
|
"kind": "Operation",
|
||||||
|
"name": "FrameworkControlPageDetachSnapshotMutation",
|
||||||
|
"selections": [
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": (v2/*: any*/),
|
||||||
|
"concreteType": "DeleteControlSnapshotMappingPayload",
|
||||||
|
"kind": "LinkedField",
|
||||||
|
"name": "deleteControlSnapshotMapping",
|
||||||
|
"plural": false,
|
||||||
|
"selections": [
|
||||||
|
(v3/*: any*/),
|
||||||
|
{
|
||||||
|
"alias": null,
|
||||||
|
"args": null,
|
||||||
|
"filters": null,
|
||||||
|
"handle": "deleteEdge",
|
||||||
|
"key": "",
|
||||||
|
"kind": "ScalarHandle",
|
||||||
|
"name": "deletedSnapshotId",
|
||||||
|
"handleArgs": [
|
||||||
|
{
|
||||||
|
"kind": "Variable",
|
||||||
|
"name": "connections",
|
||||||
|
"variableName": "connections"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"storageKey": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"cacheID": "610636745f2c0441c54bd0028fc52cf0",
|
||||||
|
"id": null,
|
||||||
|
"metadata": {},
|
||||||
|
"name": "FrameworkControlPageDetachSnapshotMutation",
|
||||||
|
"operationKind": "mutation",
|
||||||
|
"text": "mutation FrameworkControlPageDetachSnapshotMutation(\n $input: DeleteControlSnapshotMappingInput!\n) {\n deleteControlSnapshotMapping(input: $input) {\n deletedSnapshotId\n }\n}\n"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
(node as any).hash = "be8d40beadabe60d8c9bee4d99b885b7";
|
||||||
|
|
||||||
|
export default node;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useNavigate, useParams } from "react-router";
|
||||||
|
import { usePreloadedQuery, type PreloadedQuery } from "react-relay";
|
||||||
|
import { snapshotNodeQuery } from "/hooks/graph/SnapshotGraph";
|
||||||
|
import type { SnapshotGraphNodeQuery } from "/hooks/graph/__generated__/SnapshotGraphNodeQuery.graphql";
|
||||||
|
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||||
|
import { PageError } from "/components/PageError";
|
||||||
|
import { getSnapshotTypeUrlPath } from "@probo/helpers";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
queryRef: PreloadedQuery<SnapshotGraphNodeQuery>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function SnapshotDetailPage({ queryRef }: Props) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const organizationId = useOrganizationId();
|
||||||
|
const { snapshotId } = useParams();
|
||||||
|
const data = usePreloadedQuery(snapshotNodeQuery, queryRef);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!data.node || !data.node.type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const snapshot = data.node;
|
||||||
|
const snapshotType = snapshot.type;
|
||||||
|
const urlPath = getSnapshotTypeUrlPath(snapshotType);
|
||||||
|
|
||||||
|
navigate(`/organizations/${organizationId}/snapshots/${snapshotId}${urlPath}`, {
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
}, [data.node, navigate, organizationId, snapshotId]);
|
||||||
|
|
||||||
|
if (!data.node || !data.node.type) {
|
||||||
|
return <PageError />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -5,9 +5,10 @@ import {
|
|||||||
type PreloadedQuery,
|
type PreloadedQuery,
|
||||||
} from "react-relay";
|
} from "react-relay";
|
||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { getSnapshotTypeLabel } from "@probo/helpers";
|
import { getSnapshotTypeLabel, getSnapshotTypeUrlPath } from "@probo/helpers";
|
||||||
import {
|
import {
|
||||||
ActionDropdown,
|
ActionDropdown,
|
||||||
|
Badge,
|
||||||
Button,
|
Button,
|
||||||
DropdownItem,
|
DropdownItem,
|
||||||
IconPlusLarge,
|
IconPlusLarge,
|
||||||
@@ -131,28 +132,15 @@ function SnapshotRow(props: SnapshotRowProps) {
|
|||||||
const { __, dateFormat } = useTranslate();
|
const { __, dateFormat } = useTranslate();
|
||||||
const deleteSnapshot = useDeleteSnapshot(props.snapshot, props.connectionId);
|
const deleteSnapshot = useDeleteSnapshot(props.snapshot, props.connectionId);
|
||||||
|
|
||||||
const getSnapshotUrl = (snapshot: SnapshotRowProps["snapshot"]) => {
|
const typePath = getSnapshotTypeUrlPath(props.snapshot.type);
|
||||||
const baseUrl = `/organizations/${props.organizationId}/snapshots/${snapshot.id}`;
|
|
||||||
|
|
||||||
switch (snapshot.type) {
|
|
||||||
case "DATA":
|
|
||||||
return `${baseUrl}/data`;
|
|
||||||
case "VENDORS":
|
|
||||||
return `${baseUrl}/vendors`;
|
|
||||||
case "RISKS":
|
|
||||||
return `${baseUrl}/risks`;
|
|
||||||
case "ASSETS":
|
|
||||||
return `${baseUrl}/assets`;
|
|
||||||
default:
|
|
||||||
return baseUrl;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tr to={getSnapshotUrl(props.snapshot)}>
|
<Tr to={`/organizations/${props.organizationId}/snapshots/${props.snapshot.id}${typePath}`}>
|
||||||
<Td className="font-medium">{props.snapshot.name}</Td>
|
<Td className="font-medium">{props.snapshot.name}</Td>
|
||||||
<Td className="text-txt-secondary">
|
<Td>
|
||||||
|
<Badge variant="neutral">
|
||||||
{getSnapshotTypeLabel(__, props.snapshot.type)}
|
{getSnapshotTypeLabel(__, props.snapshot.type)}
|
||||||
|
</Badge>
|
||||||
</Td>
|
</Td>
|
||||||
<Td className="text-txt-secondary">
|
<Td className="text-txt-secondary">
|
||||||
{props.snapshot.description || __("No description")}
|
{props.snapshot.description || __("No description")}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { loadQuery } from "react-relay";
|
import { loadQuery } from "react-relay";
|
||||||
import { PageSkeleton } from "/components/skeletons/PageSkeleton";
|
import { PageSkeleton } from "/components/skeletons/PageSkeleton";
|
||||||
import { relayEnvironment } from "/providers/RelayProviders";
|
import { relayEnvironment } from "/providers/RelayProviders";
|
||||||
import { snapshotsQuery } from "/hooks/graph/SnapshotGraph";
|
import { snapshotsQuery, snapshotNodeQuery } from "/hooks/graph/SnapshotGraph";
|
||||||
import type { AppRoute } from "/routes";
|
import type { AppRoute } from "/routes";
|
||||||
import { lazy } from "@probo/react-lazy";
|
import { lazy } from "@probo/react-lazy";
|
||||||
|
|
||||||
@@ -15,4 +15,13 @@ export const snapshotsRoutes = [
|
|||||||
() => import("/pages/organizations/snapshots/SnapshotsPage")
|
() => import("/pages/organizations/snapshots/SnapshotsPage")
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "snapshots/:snapshotId",
|
||||||
|
fallback: PageSkeleton,
|
||||||
|
queryLoader: ({ snapshotId }) =>
|
||||||
|
loadQuery(relayEnvironment, snapshotNodeQuery, { snapshotId }),
|
||||||
|
Component: lazy(
|
||||||
|
() => import("/pages/organizations/snapshots/SnapshotDetailPage")
|
||||||
|
),
|
||||||
|
},
|
||||||
] satisfies AppRoute[];
|
] satisfies AppRoute[];
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export { certificationCategoryLabel, certifications } from "./certifications";
|
|||||||
export { availableFrameworks } from "./frameworks";
|
export { availableFrameworks } from "./frameworks";
|
||||||
export { getDocumentTypeLabel, documentTypes } from "./documents";
|
export { getDocumentTypeLabel, documentTypes } from "./documents";
|
||||||
export { getAssetTypeVariant, getCriticityVariant } from "./assets";
|
export { getAssetTypeVariant, getCriticityVariant } from "./assets";
|
||||||
export { getSnapshotTypeLabel, snapshotTypes } from "./snapshots";
|
export { getSnapshotTypeLabel, getSnapshotTypeUrlPath, snapshotTypes } from "./snapshots";
|
||||||
export { getAuditStateLabel, getAuditStateVariant, auditStates } from "./audits";
|
export { getAuditStateLabel, getAuditStateVariant, auditStates } from "./audits";
|
||||||
export { getStatusVariant, getStatusLabel, getNonconformityRegistryStatusOptions, getComplianceRegistryStatusOptions, registryStatuses } from "./registryStatus";
|
export { getStatusVariant, getStatusLabel, getNonconformityRegistryStatusOptions, getComplianceRegistryStatusOptions, registryStatuses } from "./registryStatus";
|
||||||
export { promisifyMutation } from "./relay";
|
export { promisifyMutation } from "./relay";
|
||||||
|
|||||||
@@ -24,10 +24,19 @@ export function getSnapshotTypeLabel(__: Translator, type: string | null | undef
|
|||||||
case "DATA":
|
case "DATA":
|
||||||
return __("Data");
|
return __("Data");
|
||||||
case "NON_CONFORMITY_REGISTRIES":
|
case "NON_CONFORMITY_REGISTRIES":
|
||||||
return __("Non Conformity Registries");
|
return __("Nonconformity Registries");
|
||||||
case "COMPLIANCE_REGISTRIES":
|
case "COMPLIANCE_REGISTRIES":
|
||||||
return __("Compliance Registries");
|
return __("Compliance Registries");
|
||||||
default:
|
default:
|
||||||
return __("Unknown");
|
return __("Unknown");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getSnapshotTypeUrlPath(type?: string): string {
|
||||||
|
switch (type) {
|
||||||
|
case "DATA":
|
||||||
|
return "/data";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ type (
|
|||||||
Control struct {
|
Control struct {
|
||||||
ID gid.GID `db:"id"`
|
ID gid.GID `db:"id"`
|
||||||
SectionTitle string `db:"section_title"`
|
SectionTitle string `db:"section_title"`
|
||||||
TenantID gid.TenantID `db:"tenant_id"`
|
|
||||||
FrameworkID gid.GID `db:"framework_id"`
|
FrameworkID gid.GID `db:"framework_id"`
|
||||||
Name string `db:"name"`
|
Name string `db:"name"`
|
||||||
Description string `db:"description"`
|
Description string `db:"description"`
|
||||||
@@ -137,7 +136,6 @@ SELECT
|
|||||||
id,
|
id,
|
||||||
section_title,
|
section_title,
|
||||||
framework_id,
|
framework_id,
|
||||||
tenant_id,
|
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
status,
|
status,
|
||||||
@@ -247,7 +245,6 @@ SELECT
|
|||||||
id,
|
id,
|
||||||
section_title,
|
section_title,
|
||||||
framework_id,
|
framework_id,
|
||||||
tenant_id,
|
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
status,
|
status,
|
||||||
@@ -369,7 +366,6 @@ SELECT
|
|||||||
id,
|
id,
|
||||||
section_title,
|
section_title,
|
||||||
framework_id,
|
framework_id,
|
||||||
tenant_id,
|
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
status,
|
status,
|
||||||
@@ -449,7 +445,6 @@ SELECT
|
|||||||
id,
|
id,
|
||||||
section_title,
|
section_title,
|
||||||
framework_id,
|
framework_id,
|
||||||
tenant_id,
|
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
status,
|
status,
|
||||||
@@ -562,7 +557,6 @@ SELECT
|
|||||||
id,
|
id,
|
||||||
section_title,
|
section_title,
|
||||||
framework_id,
|
framework_id,
|
||||||
tenant_id,
|
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
status,
|
status,
|
||||||
@@ -609,7 +603,6 @@ SELECT
|
|||||||
id,
|
id,
|
||||||
section_title,
|
section_title,
|
||||||
framework_id,
|
framework_id,
|
||||||
tenant_id,
|
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
status,
|
status,
|
||||||
@@ -654,7 +647,6 @@ SELECT
|
|||||||
id,
|
id,
|
||||||
section_title,
|
section_title,
|
||||||
framework_id,
|
framework_id,
|
||||||
tenant_id,
|
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
status,
|
status,
|
||||||
@@ -777,7 +769,6 @@ WHERE %s
|
|||||||
RETURNING
|
RETURNING
|
||||||
id,
|
id,
|
||||||
framework_id,
|
framework_id,
|
||||||
tenant_id,
|
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
section_title,
|
section_title,
|
||||||
@@ -903,7 +894,6 @@ SELECT
|
|||||||
id,
|
id,
|
||||||
section_title,
|
section_title,
|
||||||
framework_id,
|
framework_id,
|
||||||
tenant_id,
|
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
status,
|
status,
|
||||||
@@ -937,3 +927,113 @@ WHERE %s
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Controls) CountBySnapshotID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
scope Scoper,
|
||||||
|
snapshotID 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_snapshots cs ON c.id = cs.control_id
|
||||||
|
WHERE
|
||||||
|
cs.snapshot_id = @snapshot_id
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
COUNT(id)
|
||||||
|
FROM
|
||||||
|
ctrl
|
||||||
|
WHERE %s
|
||||||
|
AND %s
|
||||||
|
`
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.NamedArgs{"snapshot_id": snapshotID}
|
||||||
|
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) LoadBySnapshotID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
scope Scoper,
|
||||||
|
snapshotID 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_snapshots cs ON c.id = cs.control_id
|
||||||
|
WHERE
|
||||||
|
cs.snapshot_id = @snapshot_id
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
section_title,
|
||||||
|
framework_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{"snapshot_id": snapshotID}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|||||||
168
pkg/coredata/control_snapshot.go
Normal file
168
pkg/coredata/control_snapshot.go
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// 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 (
|
||||||
|
ControlSnapshot struct {
|
||||||
|
ControlID gid.GID `db:"control_id"`
|
||||||
|
SnapshotID gid.GID `db:"snapshot_id"`
|
||||||
|
CreatedAt time.Time `db:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
ControlSnapshots []*ControlSnapshot
|
||||||
|
)
|
||||||
|
|
||||||
|
func (cs ControlSnapshot) Upsert(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
scope Scoper,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
INSERT INTO
|
||||||
|
controls_snapshots (
|
||||||
|
control_id,
|
||||||
|
snapshot_id,
|
||||||
|
tenant_id,
|
||||||
|
created_at
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
@control_id,
|
||||||
|
@snapshot_id,
|
||||||
|
@tenant_id,
|
||||||
|
@created_at
|
||||||
|
)
|
||||||
|
ON CONFLICT (control_id, snapshot_id) DO NOTHING;
|
||||||
|
`
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"control_id": cs.ControlID,
|
||||||
|
"snapshot_id": cs.SnapshotID,
|
||||||
|
"tenant_id": scope.GetTenantID(),
|
||||||
|
"created_at": cs.CreatedAt,
|
||||||
|
}
|
||||||
|
_, err := conn.Exec(ctx, q, args)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs ControlSnapshot) Delete(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
scope Scoper,
|
||||||
|
controlID gid.GID,
|
||||||
|
snapshotID gid.GID,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
DELETE
|
||||||
|
FROM
|
||||||
|
controls_snapshots
|
||||||
|
WHERE
|
||||||
|
%s
|
||||||
|
AND control_id = @control_id
|
||||||
|
AND snapshot_id = @snapshot_id;
|
||||||
|
`
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{
|
||||||
|
"control_id": controlID,
|
||||||
|
"snapshot_id": snapshotID,
|
||||||
|
}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||||
|
|
||||||
|
_, err := conn.Exec(ctx, q, args)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (css *ControlSnapshots) LoadByControlID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
scope Scoper,
|
||||||
|
controlID gid.GID,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
SELECT
|
||||||
|
control_id,
|
||||||
|
snapshot_id,
|
||||||
|
created_at
|
||||||
|
FROM
|
||||||
|
controls_snapshots
|
||||||
|
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 controls_snapshots: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
controlSnapshots, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[ControlSnapshot])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot collect controls_snapshots: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*css = controlSnapshots
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (css *ControlSnapshots) LoadBySnapshotID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
scope Scoper,
|
||||||
|
snapshotID gid.GID,
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
SELECT
|
||||||
|
control_id,
|
||||||
|
snapshot_id,
|
||||||
|
created_at
|
||||||
|
FROM
|
||||||
|
controls_snapshots
|
||||||
|
WHERE
|
||||||
|
%s
|
||||||
|
AND snapshot_id = @snapshot_id
|
||||||
|
`
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{"snapshot_id": snapshotID}
|
||||||
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
|
||||||
|
rows, err := conn.Query(ctx, q, args)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot query controls_snapshots: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
controlSnapshots, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[ControlSnapshot])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot collect controls_snapshots: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*css = controlSnapshots
|
||||||
|
return nil
|
||||||
|
}
|
||||||
15
pkg/coredata/migrations/20250826T174137Z.sql
Normal file
15
pkg/coredata/migrations/20250826T174137Z.sql
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
CREATE TABLE controls_snapshots (
|
||||||
|
control_id TEXT NOT NULL REFERENCES controls(id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
snapshot_id TEXT NOT NULL REFERENCES snapshots(id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
tenant_id TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||||
|
PRIMARY KEY (control_id, snapshot_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
ALTER TABLE controls_audits DROP CONSTRAINT controls_audits_control_id_fkey;
|
||||||
|
ALTER TABLE controls_audits ADD CONSTRAINT controls_audits_control_id_fkey
|
||||||
|
FOREIGN KEY (control_id) REFERENCES controls(id) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
ALTER TABLE controls_audits DROP CONSTRAINT controls_audits_audit_id_fkey;
|
||||||
|
ALTER TABLE controls_audits ADD CONSTRAINT controls_audits_audit_id_fkey
|
||||||
|
FOREIGN KEY (audit_id) REFERENCES audits(id) ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -237,3 +237,60 @@ WHERE
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Snapshots) LoadByControlID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Conn,
|
||||||
|
scope Scoper,
|
||||||
|
controlID gid.GID,
|
||||||
|
cursor *page.Cursor[SnapshotOrderField],
|
||||||
|
) error {
|
||||||
|
q := `
|
||||||
|
WITH snapshots_by_control AS (
|
||||||
|
SELECT
|
||||||
|
s.id,
|
||||||
|
s.tenant_id,
|
||||||
|
s.organization_id,
|
||||||
|
s.name,
|
||||||
|
s.description,
|
||||||
|
s.type,
|
||||||
|
s.created_at
|
||||||
|
FROM
|
||||||
|
snapshots s
|
||||||
|
INNER JOIN
|
||||||
|
controls_snapshots cs ON s.id = cs.snapshot_id
|
||||||
|
WHERE
|
||||||
|
cs.control_id = @control_id
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
organization_id,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
type,
|
||||||
|
created_at
|
||||||
|
FROM
|
||||||
|
snapshots_by_control
|
||||||
|
WHERE %s
|
||||||
|
AND %s
|
||||||
|
`
|
||||||
|
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
||||||
|
|
||||||
|
args := pgx.StrictNamedArgs{"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 snapshots: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshots, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[Snapshot])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot collect snapshots: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*s = snapshots
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -597,6 +597,111 @@ func (s ControlService) ListForAuditID(
|
|||||||
return page.NewPage([]*coredata.Control(controls), cursor), nil
|
return page.NewPage([]*coredata.Control(controls), cursor), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s ControlService) CreateSnapshotMapping(
|
||||||
|
ctx context.Context,
|
||||||
|
controlID gid.GID,
|
||||||
|
snapshotID gid.GID,
|
||||||
|
) (*coredata.Control, *coredata.Snapshot, error) {
|
||||||
|
controlSnapshot := &coredata.ControlSnapshot{
|
||||||
|
ControlID: controlID,
|
||||||
|
SnapshotID: snapshotID,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
control := &coredata.Control{}
|
||||||
|
snapshot := &coredata.Snapshot{}
|
||||||
|
|
||||||
|
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 := snapshot.LoadByID(ctx, conn, s.svc.scope, snapshotID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load snapshot: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := controlSnapshot.Upsert(ctx, conn, s.svc.scope); err != nil {
|
||||||
|
return fmt.Errorf("cannot create control snapshot mapping: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return control, snapshot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ControlService) DeleteSnapshotMapping(
|
||||||
|
ctx context.Context,
|
||||||
|
controlID gid.GID,
|
||||||
|
snapshotID gid.GID,
|
||||||
|
) (*coredata.Control, *coredata.Snapshot, error) {
|
||||||
|
control := &coredata.Control{}
|
||||||
|
snapshot := &coredata.Snapshot{}
|
||||||
|
|
||||||
|
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 := snapshot.LoadByID(ctx, conn, s.svc.scope, snapshotID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load snapshot: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
controlSnapshot := &coredata.ControlSnapshot{}
|
||||||
|
if err := controlSnapshot.Delete(ctx, conn, s.svc.scope, control.ID, snapshot.ID); err != nil {
|
||||||
|
return fmt.Errorf("cannot delete control snapshot mapping: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("cannot delete control snapshot mapping: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return control, snapshot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s ControlService) ListForSnapshotID(
|
||||||
|
ctx context.Context,
|
||||||
|
snapshotID gid.GID,
|
||||||
|
cursor *page.Cursor[coredata.ControlOrderField],
|
||||||
|
filter *coredata.ControlFilter,
|
||||||
|
) (*page.Page[*coredata.Control, coredata.ControlOrderField], error) {
|
||||||
|
var controls coredata.Controls
|
||||||
|
snapshot := &coredata.Snapshot{}
|
||||||
|
|
||||||
|
err := s.svc.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(conn pg.Conn) error {
|
||||||
|
if err := snapshot.LoadByID(ctx, conn, s.svc.scope, snapshotID); err != nil {
|
||||||
|
return fmt.Errorf("cannot load snapshot: %w", err)
|
||||||
|
}
|
||||||
|
if err := controls.LoadBySnapshotID(ctx, conn, s.svc.scope, snapshotID, 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(
|
func (s ControlService) Create(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req CreateControlRequest,
|
req CreateControlRequest,
|
||||||
@@ -607,7 +712,6 @@ func (s ControlService) Create(
|
|||||||
control := &coredata.Control{
|
control := &coredata.Control{
|
||||||
ID: gid.New(s.svc.scope.GetTenantID(), coredata.ControlEntityType),
|
ID: gid.New(s.svc.scope.GetTenantID(), coredata.ControlEntityType),
|
||||||
FrameworkID: req.FrameworkID,
|
FrameworkID: req.FrameworkID,
|
||||||
TenantID: s.svc.scope.GetTenantID(),
|
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Description: req.Description,
|
Description: req.Description,
|
||||||
SectionTitle: req.SectionTitle,
|
SectionTitle: req.SectionTitle,
|
||||||
|
|||||||
@@ -248,7 +248,6 @@ func (s FrameworkService) Import(
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
control := &coredata.Control{
|
control := &coredata.Control{
|
||||||
ID: controlID,
|
ID: controlID,
|
||||||
TenantID: organizationID.TenantID(),
|
|
||||||
FrameworkID: frameworkID,
|
FrameworkID: frameworkID,
|
||||||
SectionTitle: control.ID,
|
SectionTitle: control.ID,
|
||||||
Name: control.Name,
|
Name: control.Name,
|
||||||
|
|||||||
@@ -184,3 +184,34 @@ func (s *SnapshotService) CountForOrganizationID(
|
|||||||
|
|
||||||
return count, nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SnapshotService) ListForControlID(
|
||||||
|
ctx context.Context,
|
||||||
|
controlID gid.GID,
|
||||||
|
cursor *page.Cursor[coredata.SnapshotOrderField],
|
||||||
|
) (*page.Page[*coredata.Snapshot, coredata.SnapshotOrderField], error) {
|
||||||
|
var snapshots coredata.Snapshots
|
||||||
|
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 := snapshots.LoadByControlID(ctx, conn, s.svc.scope, control.ID, cursor)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot load snapshots: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return page.NewPage(snapshots, cursor), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1300,6 +1300,14 @@ type Control implements Node {
|
|||||||
orderBy: AuditOrder
|
orderBy: AuditOrder
|
||||||
): AuditConnection! @goField(forceResolver: true)
|
): AuditConnection! @goField(forceResolver: true)
|
||||||
|
|
||||||
|
snapshots(
|
||||||
|
first: Int
|
||||||
|
after: CursorKey
|
||||||
|
last: Int
|
||||||
|
before: CursorKey
|
||||||
|
orderBy: SnapshotOrder
|
||||||
|
): SnapshotConnection! @goField(forceResolver: true)
|
||||||
|
|
||||||
createdAt: Datetime!
|
createdAt: Datetime!
|
||||||
updatedAt: Datetime!
|
updatedAt: Datetime!
|
||||||
}
|
}
|
||||||
@@ -1537,6 +1545,16 @@ type Snapshot implements Node {
|
|||||||
name: String!
|
name: String!
|
||||||
description: String
|
description: String
|
||||||
type: SnapshotsType!
|
type: SnapshotsType!
|
||||||
|
|
||||||
|
controls(
|
||||||
|
first: Int
|
||||||
|
after: CursorKey
|
||||||
|
last: Int
|
||||||
|
before: CursorKey
|
||||||
|
orderBy: ControlOrder
|
||||||
|
filter: ControlFilter
|
||||||
|
): ControlConnection! @goField(forceResolver: true)
|
||||||
|
|
||||||
createdAt: Datetime!
|
createdAt: Datetime!
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1982,6 +2000,12 @@ type Mutation {
|
|||||||
deleteControlAuditMapping(
|
deleteControlAuditMapping(
|
||||||
input: DeleteControlAuditMappingInput!
|
input: DeleteControlAuditMappingInput!
|
||||||
): DeleteControlAuditMappingPayload!
|
): DeleteControlAuditMappingPayload!
|
||||||
|
createControlSnapshotMapping(
|
||||||
|
input: CreateControlSnapshotMappingInput!
|
||||||
|
): CreateControlSnapshotMappingPayload!
|
||||||
|
deleteControlSnapshotMapping(
|
||||||
|
input: DeleteControlSnapshotMappingInput!
|
||||||
|
): DeleteControlSnapshotMappingPayload!
|
||||||
|
|
||||||
# Task mutations
|
# Task mutations
|
||||||
createTask(input: CreateTaskInput!): CreateTaskPayload!
|
createTask(input: CreateTaskInput!): CreateTaskPayload!
|
||||||
@@ -2396,6 +2420,16 @@ input DeleteControlAuditMappingInput {
|
|||||||
auditId: ID!
|
auditId: ID!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input CreateControlSnapshotMappingInput {
|
||||||
|
controlId: ID!
|
||||||
|
snapshotId: ID!
|
||||||
|
}
|
||||||
|
|
||||||
|
input DeleteControlSnapshotMappingInput {
|
||||||
|
controlId: ID!
|
||||||
|
snapshotId: ID!
|
||||||
|
}
|
||||||
|
|
||||||
input CreateRiskInput {
|
input CreateRiskInput {
|
||||||
organizationId: ID!
|
organizationId: ID!
|
||||||
name: String!
|
name: String!
|
||||||
@@ -2865,6 +2899,16 @@ type DeleteControlAuditMappingPayload {
|
|||||||
deletedAuditId: ID!
|
deletedAuditId: ID!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateControlSnapshotMappingPayload {
|
||||||
|
controlEdge: ControlEdge!
|
||||||
|
snapshotEdge: SnapshotEdge!
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteControlSnapshotMappingPayload {
|
||||||
|
deletedControlId: ID!
|
||||||
|
deletedSnapshotId: ID!
|
||||||
|
}
|
||||||
|
|
||||||
type CreateRiskPayload {
|
type CreateRiskPayload {
|
||||||
riskEdge: RiskEdge!
|
riskEdge: RiskEdge!
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -178,6 +178,7 @@ type Control struct {
|
|||||||
Measures *MeasureConnection `json:"measures"`
|
Measures *MeasureConnection `json:"measures"`
|
||||||
Documents *DocumentConnection `json:"documents"`
|
Documents *DocumentConnection `json:"documents"`
|
||||||
Audits *AuditConnection `json:"audits"`
|
Audits *AuditConnection `json:"audits"`
|
||||||
|
Snapshots *SnapshotConnection `json:"snapshots"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
}
|
}
|
||||||
@@ -284,6 +285,16 @@ type CreateControlPayload struct {
|
|||||||
ControlEdge *ControlEdge `json:"controlEdge"`
|
ControlEdge *ControlEdge `json:"controlEdge"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateControlSnapshotMappingInput struct {
|
||||||
|
ControlID gid.GID `json:"controlId"`
|
||||||
|
SnapshotID gid.GID `json:"snapshotId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateControlSnapshotMappingPayload struct {
|
||||||
|
ControlEdge *ControlEdge `json:"controlEdge"`
|
||||||
|
SnapshotEdge *SnapshotEdge `json:"snapshotEdge"`
|
||||||
|
}
|
||||||
|
|
||||||
type CreateDatumInput struct {
|
type CreateDatumInput struct {
|
||||||
OrganizationID gid.GID `json:"organizationId"`
|
OrganizationID gid.GID `json:"organizationId"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
@@ -622,6 +633,16 @@ type DeleteControlPayload struct {
|
|||||||
DeletedControlID gid.GID `json:"deletedControlId"`
|
DeletedControlID gid.GID `json:"deletedControlId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeleteControlSnapshotMappingInput struct {
|
||||||
|
ControlID gid.GID `json:"controlId"`
|
||||||
|
SnapshotID gid.GID `json:"snapshotId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteControlSnapshotMappingPayload struct {
|
||||||
|
DeletedControlID gid.GID `json:"deletedControlId"`
|
||||||
|
DeletedSnapshotID gid.GID `json:"deletedSnapshotId"`
|
||||||
|
}
|
||||||
|
|
||||||
type DeleteDatumInput struct {
|
type DeleteDatumInput struct {
|
||||||
DatumID gid.GID `json:"datumId"`
|
DatumID gid.GID `json:"datumId"`
|
||||||
}
|
}
|
||||||
@@ -1231,6 +1252,7 @@ type Snapshot struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Description *string `json:"description,omitempty"`
|
Description *string `json:"description,omitempty"`
|
||||||
Type coredata.SnapshotsType `json:"type"`
|
Type coredata.SnapshotsType `json:"type"`
|
||||||
|
Controls *ControlConnection `json:"controls"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -389,6 +389,32 @@ func (r *controlResolver) Audits(ctx context.Context, obj *types.Control, first
|
|||||||
return types.NewAuditConnection(page, r, obj.ID), nil
|
return types.NewAuditConnection(page, r, obj.ID), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Snapshots is the resolver for the snapshots field.
|
||||||
|
func (r *controlResolver) Snapshots(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.SnapshotOrderBy) (*types.SnapshotConnection, error) {
|
||||||
|
prb := r.ProboService(ctx, obj.ID.TenantID())
|
||||||
|
|
||||||
|
pageOrderBy := page.OrderBy[coredata.SnapshotOrderField]{
|
||||||
|
Field: coredata.SnapshotOrderFieldCreatedAt,
|
||||||
|
Direction: page.OrderDirectionDesc,
|
||||||
|
}
|
||||||
|
|
||||||
|
if orderBy != nil {
|
||||||
|
pageOrderBy = page.OrderBy[coredata.SnapshotOrderField]{
|
||||||
|
Field: orderBy.Field,
|
||||||
|
Direction: orderBy.Direction,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor := types.NewCursor(first, after, last, before, pageOrderBy)
|
||||||
|
|
||||||
|
page, err := prb.Snapshots.ListForControlID(ctx, obj.ID, cursor)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("cannot list control snapshots: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.NewSnapshotConnection(page, r, obj.ID), nil
|
||||||
|
}
|
||||||
|
|
||||||
// TotalCount is the resolver for the totalCount field.
|
// TotalCount is the resolver for the totalCount field.
|
||||||
func (r *controlConnectionResolver) TotalCount(ctx context.Context, obj *types.ControlConnection) (int, error) {
|
func (r *controlConnectionResolver) TotalCount(ctx context.Context, obj *types.ControlConnection) (int, error) {
|
||||||
prb := r.ProboService(ctx, obj.ParentID.TenantID())
|
prb := r.ProboService(ctx, obj.ParentID.TenantID())
|
||||||
@@ -1823,6 +1849,36 @@ func (r *mutationResolver) DeleteControlAuditMapping(ctx context.Context, input
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateControlSnapshotMapping is the resolver for the createControlSnapshotMapping field.
|
||||||
|
func (r *mutationResolver) CreateControlSnapshotMapping(ctx context.Context, input types.CreateControlSnapshotMappingInput) (*types.CreateControlSnapshotMappingPayload, error) {
|
||||||
|
prb := r.ProboService(ctx, input.SnapshotID.TenantID())
|
||||||
|
|
||||||
|
control, snapshot, err := prb.Controls.CreateSnapshotMapping(ctx, input.ControlID, input.SnapshotID)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("cannot create control snapshot mapping: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.CreateControlSnapshotMappingPayload{
|
||||||
|
ControlEdge: types.NewControlEdge(control, coredata.ControlOrderFieldCreatedAt),
|
||||||
|
SnapshotEdge: types.NewSnapshotEdge(snapshot, coredata.SnapshotOrderFieldCreatedAt),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteControlSnapshotMapping is the resolver for the deleteControlSnapshotMapping field.
|
||||||
|
func (r *mutationResolver) DeleteControlSnapshotMapping(ctx context.Context, input types.DeleteControlSnapshotMappingInput) (*types.DeleteControlSnapshotMappingPayload, error) {
|
||||||
|
prb := r.ProboService(ctx, input.SnapshotID.TenantID())
|
||||||
|
|
||||||
|
control, snapshot, err := prb.Controls.DeleteSnapshotMapping(ctx, input.ControlID, input.SnapshotID)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("cannot delete control snapshot mapping: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.DeleteControlSnapshotMappingPayload{
|
||||||
|
DeletedControlID: control.ID,
|
||||||
|
DeletedSnapshotID: snapshot.ID,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
// CreateTask is the resolver for the createTask field.
|
// CreateTask is the resolver for the createTask field.
|
||||||
func (r *mutationResolver) CreateTask(ctx context.Context, input types.CreateTaskInput) (*types.CreateTaskPayload, error) {
|
func (r *mutationResolver) CreateTask(ctx context.Context, input types.CreateTaskInput) (*types.CreateTaskPayload, error) {
|
||||||
prb := r.ProboService(ctx, input.MeasureID.TenantID())
|
prb := r.ProboService(ctx, input.MeasureID.TenantID())
|
||||||
@@ -3910,6 +3966,36 @@ func (r *snapshotResolver) Organization(ctx context.Context, obj *types.Snapshot
|
|||||||
return types.NewOrganization(organization), nil
|
return types.NewOrganization(organization), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Controls is the resolver for the controls field.
|
||||||
|
func (r *snapshotResolver) Controls(ctx context.Context, obj *types.Snapshot, 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.ListForSnapshotID(ctx, obj.ID, cursor, controlFilter)
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("cannot list snapshot controls: %w", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return types.NewControlConnection(page, r, obj.ID, controlFilter), nil
|
||||||
|
}
|
||||||
|
|
||||||
// TotalCount is the resolver for the totalCount field.
|
// TotalCount is the resolver for the totalCount field.
|
||||||
func (r *snapshotConnectionResolver) TotalCount(ctx context.Context, obj *types.SnapshotConnection) (int, error) {
|
func (r *snapshotConnectionResolver) TotalCount(ctx context.Context, obj *types.SnapshotConnection) (int, error) {
|
||||||
prb := r.ProboService(ctx, obj.ParentID.TenantID())
|
prb := r.ProboService(ctx, obj.ParentID.TenantID())
|
||||||
|
|||||||
Reference in New Issue
Block a user