Add frameworks detail page
Signed-off-by: Bryan Frimin <bryan@getprobo.com> Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
committed by
Sacha Al Himdani
parent
b6aec0b14a
commit
74c136f6c0
15
apps/console2/src/components/FrameworkLogo.tsx
Normal file
15
apps/console2/src/components/FrameworkLogo.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { availableFrameworks } from "@probo/helpers";
|
||||
import { Avatar } from "@probo/ui";
|
||||
|
||||
const availableLogos = new Map(
|
||||
availableFrameworks.map((framework) => [framework.name, framework.logo])
|
||||
);
|
||||
|
||||
export function FrameworkLogo({ name }: { name: string }) {
|
||||
const logo = availableLogos.get(name);
|
||||
return logo ? (
|
||||
<img src={logo} alt="" className="size-12" />
|
||||
) : (
|
||||
<Avatar name={name} size="l" className="size-12" />
|
||||
);
|
||||
}
|
||||
138
apps/console2/src/components/documents/DocumentLinkDialog.tsx
Normal file
138
apps/console2/src/components/documents/DocumentLinkDialog.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
IconMagnifyingGlass,
|
||||
IconPlusLarge,
|
||||
IconTrashCan,
|
||||
Input,
|
||||
Spinner,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Suspense, useMemo, useState, type ReactNode } from "react";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { useLazyLoadQuery } from "react-relay";
|
||||
import type { DocumentLinkDialogQuery } from "./__generated__/DocumentLinkDialogQuery.graphql";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
|
||||
const documentsQuery = graphql`
|
||||
query DocumentLinkDialogQuery($organizationId: ID!) {
|
||||
organization: node(id: $organizationId) {
|
||||
id
|
||||
... on Organization {
|
||||
documents(first: 100) @connection(key: "Organization__documents") {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
connectionId: string;
|
||||
disabled?: boolean;
|
||||
linkedDocuments?: { id: string }[];
|
||||
onLink: (documentId: string) => void;
|
||||
onUnlink: (documentId: string) => void;
|
||||
};
|
||||
|
||||
export function DocumentLinkDialog({ children, ...props }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
|
||||
return (
|
||||
<Dialog trigger={children} title={__("Link documents")}>
|
||||
<DialogContent>
|
||||
<Suspense fallback={<Spinner centered />}>
|
||||
<DocumentLinkDialogContent {...props} />
|
||||
</Suspense>
|
||||
</DialogContent>
|
||||
<DialogFooter exitLabel={__("Close")} />
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function DocumentLinkDialogContent(props: Omit<Props, "children">) {
|
||||
const organizationId = useOrganizationId();
|
||||
const data = useLazyLoadQuery<DocumentLinkDialogQuery>(documentsQuery, {
|
||||
organizationId,
|
||||
});
|
||||
const { __ } = useTranslate();
|
||||
const [search, setSearch] = useState("");
|
||||
const documents =
|
||||
data.organization?.documents?.edges?.map((edge) => edge.node) ?? [];
|
||||
const linkedIds = useMemo(() => {
|
||||
return new Set(props.linkedDocuments?.map((m) => m.id) ?? []);
|
||||
}, [props.linkedDocuments]);
|
||||
|
||||
const filteredDocuments = useMemo(() => {
|
||||
return documents.filter((document) =>
|
||||
document.title.toLowerCase().includes(search.toLowerCase())
|
||||
);
|
||||
}, [documents, 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 documents...")}
|
||||
onValueChange={setSearch}
|
||||
/>
|
||||
</div>
|
||||
<div className="divide-y divide-border-low">
|
||||
{filteredDocuments.map((document) => (
|
||||
<DocumentRow
|
||||
key={document.id}
|
||||
document={document}
|
||||
linkedDocuments={linkedIds}
|
||||
onLink={props.onLink}
|
||||
onUnlink={props.onUnlink}
|
||||
disabled={props.disabled}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type RowProps = {
|
||||
document: { title: string; id: string };
|
||||
linkedDocuments: Set<string>;
|
||||
disabled?: boolean;
|
||||
onLink: (documentId: string) => void;
|
||||
onUnlink: (documentId: string) => void;
|
||||
};
|
||||
|
||||
function DocumentRow(props: RowProps) {
|
||||
const { __ } = useTranslate();
|
||||
|
||||
const isLinked = props.linkedDocuments.has(props.document.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.document.id)}
|
||||
>
|
||||
{props.document.title}
|
||||
<Button
|
||||
disabled={props.disabled}
|
||||
className="ml-auto"
|
||||
variant={isLinked ? "secondary" : "primary"}
|
||||
asChild
|
||||
>
|
||||
<span>
|
||||
<IconComponent size={16} /> {isLinked ? __("Unlink") : __("Link")}
|
||||
</span>
|
||||
</Button>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
189
apps/console2/src/components/documents/LinkedDocumentsCard.tsx
Normal file
189
apps/console2/src/components/documents/LinkedDocumentsCard.tsx
Normal file
@@ -0,0 +1,189 @@
|
||||
import { graphql } from "relay-runtime";
|
||||
import {
|
||||
Card,
|
||||
IconPlusLarge,
|
||||
Button,
|
||||
Tr,
|
||||
Td,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Th,
|
||||
IconChevronDown,
|
||||
IconTrashCan,
|
||||
DocumentVersionBadge,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import type { LinkedDocumentsCardFragment$key } from "./__generated__/LinkedDocumentsCardFragment.graphql";
|
||||
import { useFragment } from "react-relay";
|
||||
import { useMemo, useState } from "react";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import { DocumentLinkDialog } from "./DocumentLinkDialog";
|
||||
|
||||
const linkedDocumentFragment = graphql`
|
||||
fragment LinkedDocumentsCardFragment on Document {
|
||||
id
|
||||
title
|
||||
createdAt
|
||||
versions(first: 1) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type Mutation<Params> = (p: {
|
||||
variables: {
|
||||
input: {
|
||||
documentId: string;
|
||||
} & Params;
|
||||
connections: string[];
|
||||
};
|
||||
}) => void;
|
||||
|
||||
type Props<Params> = {
|
||||
// Documents linked to the element
|
||||
documents: (LinkedDocumentsCardFragment$key & { id: string })[];
|
||||
// Extra params to send to the mutation
|
||||
params: Params;
|
||||
// Disable (action when loading for instance)
|
||||
disabled?: boolean;
|
||||
// ID of the connection to update
|
||||
connectionId: string;
|
||||
// Mutation to attach a document (will receive {documentId, ...params})
|
||||
onAttach: Mutation<Params>;
|
||||
// Mutation to detach a document (will receive {documentId, ...params})
|
||||
onDetach: Mutation<Params>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reusable component that displays a list of linked documents
|
||||
*/
|
||||
export function LinkedDocumentsCard<Params>(props: Props<Params>) {
|
||||
const { __ } = useTranslate();
|
||||
const [limit, setLimit] = useState<number | null>(4);
|
||||
const documents = useMemo(() => {
|
||||
return limit ? props.documents.slice(0, limit) : props.documents;
|
||||
}, [props.documents, limit]);
|
||||
const showMoreButton = limit !== null && props.documents.length > limit;
|
||||
|
||||
const onAttach = (documentId: string) => {
|
||||
props.onAttach({
|
||||
variables: {
|
||||
input: {
|
||||
documentId,
|
||||
...props.params,
|
||||
},
|
||||
connections: [props.connectionId],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onDetach = (documentId: string) => {
|
||||
props.onDetach({
|
||||
variables: {
|
||||
input: {
|
||||
documentId,
|
||||
...props.params,
|
||||
},
|
||||
connections: [props.connectionId],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card padded className="space-y-[10px]">
|
||||
<div className="flex justify-between">
|
||||
<div className="text-lg font-semibold">{__("Documents")}</div>
|
||||
<DocumentLinkDialog
|
||||
connectionId={props.connectionId}
|
||||
disabled={props.disabled}
|
||||
linkedDocuments={props.documents}
|
||||
onLink={onAttach}
|
||||
onUnlink={onDetach}
|
||||
>
|
||||
<Button variant="tertiary" icon={IconPlusLarge}>
|
||||
{__("Link document")}
|
||||
</Button>
|
||||
</DocumentLinkDialog>
|
||||
</div>
|
||||
{documents.length > 0 ? (
|
||||
<Table className="bg-invert">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{__("Name")}</Th>
|
||||
<Th>{__("State")}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{documents.map((document) => (
|
||||
<DocumentRow
|
||||
key={document.id}
|
||||
document={document}
|
||||
onClick={onDetach}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="text-center text-sm text-txt-secondary">
|
||||
{__("No documents linked")}
|
||||
</div>
|
||||
)}
|
||||
{showMoreButton && (
|
||||
<Button
|
||||
variant="tertiary"
|
||||
onClick={() => setLimit(null)}
|
||||
className="mt-3 mx-auto"
|
||||
icon={IconChevronDown}
|
||||
>
|
||||
{sprintf(__("Show %s more"), props.documents.length - limit)}
|
||||
</Button>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function DocumentRow(props: {
|
||||
document: LinkedDocumentsCardFragment$key & { id: string };
|
||||
onClick: (documentId: string) => void;
|
||||
}) {
|
||||
const document = useFragment(linkedDocumentFragment, props.document);
|
||||
const organizationId = useOrganizationId();
|
||||
const { __ } = useTranslate();
|
||||
|
||||
return (
|
||||
<Tr to={`/organizations/${organizationId}/documents/${document.id}`}>
|
||||
<Td>
|
||||
<div className="flex gap-4 items-center">
|
||||
<img
|
||||
src="/document.png"
|
||||
alt=""
|
||||
width={28}
|
||||
height={36}
|
||||
className="border-4 border-highlight rounded box-content"
|
||||
/>
|
||||
{document.title}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<DocumentVersionBadge state={document.versions.edges[0].node.status} />
|
||||
</Td>
|
||||
<Td noLink width={50} className="text-end">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => props.onClick(document.id)}
|
||||
icon={IconTrashCan}
|
||||
>
|
||||
{__("Unlink")}
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
246
apps/console2/src/components/documents/__generated__/DocumentLinkDialogQuery.graphql.ts
generated
Normal file
246
apps/console2/src/components/documents/__generated__/DocumentLinkDialogQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,246 @@
|
||||
/**
|
||||
* @generated SignedSource<<dfc2f49ef0ca7d3a3e62809aef61dfe6>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type DocumentLinkDialogQuery$variables = {
|
||||
organizationId: string;
|
||||
};
|
||||
export type DocumentLinkDialogQuery$data = {
|
||||
readonly organization: {
|
||||
readonly documents?: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly title: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly id: string;
|
||||
};
|
||||
};
|
||||
export type DocumentLinkDialogQuery = {
|
||||
response: DocumentLinkDialogQuery$data;
|
||||
variables: DocumentLinkDialogQuery$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 = [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Document",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "title",
|
||||
"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
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
v5 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 100
|
||||
}
|
||||
];
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "DocumentLinkDialogQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "organization",
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
{
|
||||
"alias": "documents",
|
||||
"args": null,
|
||||
"concreteType": "DocumentConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "__Organization__documents_connection",
|
||||
"plural": false,
|
||||
"selections": (v4/*: any*/),
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "DocumentLinkDialogQuery",
|
||||
"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": (v5/*: any*/),
|
||||
"concreteType": "DocumentConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "documents",
|
||||
"plural": false,
|
||||
"selections": (v4/*: any*/),
|
||||
"storageKey": "documents(first:100)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v5/*: any*/),
|
||||
"filters": null,
|
||||
"handle": "connection",
|
||||
"key": "Organization__documents",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "documents"
|
||||
}
|
||||
],
|
||||
"type": "Organization",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "fa671324c6ab818f40ccf9f83ca9af8a",
|
||||
"id": null,
|
||||
"metadata": {
|
||||
"connection": [
|
||||
{
|
||||
"count": null,
|
||||
"cursor": null,
|
||||
"direction": "forward",
|
||||
"path": [
|
||||
"organization",
|
||||
"documents"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "DocumentLinkDialogQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query DocumentLinkDialogQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n documents(first: 100) {\n edges {\n node {\n id\n title\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "70a09260215ae0f74ccf06a0c0872e75";
|
||||
|
||||
export default node;
|
||||
117
apps/console2/src/components/documents/__generated__/LinkedDocumentsCardFragment.graphql.ts
generated
Normal file
117
apps/console2/src/components/documents/__generated__/LinkedDocumentsCardFragment.graphql.ts
generated
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* @generated SignedSource<<da0435f63c82413be14bb2bb444aad49>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
export type DocumentStatus = "DRAFT" | "PUBLISHED";
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type LinkedDocumentsCardFragment$data = {
|
||||
readonly createdAt: any;
|
||||
readonly id: string;
|
||||
readonly title: string;
|
||||
readonly versions: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly status: DocumentStatus;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly " $fragmentType": "LinkedDocumentsCardFragment";
|
||||
};
|
||||
export type LinkedDocumentsCardFragment$key = {
|
||||
readonly " $data"?: LinkedDocumentsCardFragment$data;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"LinkedDocumentsCardFragment">;
|
||||
};
|
||||
|
||||
const node: ReaderFragment = (function(){
|
||||
var v0 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "LinkedDocumentsCardFragment",
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 1
|
||||
}
|
||||
],
|
||||
"concreteType": "DocumentVersionConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "versions",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentVersionEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentVersion",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v0/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "versions(first:1)"
|
||||
}
|
||||
],
|
||||
"type": "Document",
|
||||
"abstractKey": null
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "907fbdc23df596e4f76e396a11878706";
|
||||
|
||||
export default node;
|
||||
170
apps/console2/src/components/measures/LinkedMeasuresCard.tsx
Normal file
170
apps/console2/src/components/measures/LinkedMeasuresCard.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
import { graphql } from "relay-runtime";
|
||||
import {
|
||||
Card,
|
||||
IconPlusLarge,
|
||||
Button,
|
||||
Tr,
|
||||
Td,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Th,
|
||||
IconChevronDown,
|
||||
MeasureBadge,
|
||||
IconTrashCan,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import type { LinkedMeasuresCardFragment$key } from "./__generated__/LinkedMeasuresCardFragment.graphql";
|
||||
import { useFragment } from "react-relay";
|
||||
import { useMemo, useState } from "react";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import { MeasureLinkDialog } from "./MeasureLinkDialog";
|
||||
|
||||
const linkedMeasureFragment = graphql`
|
||||
fragment LinkedMeasuresCardFragment on Measure {
|
||||
id
|
||||
name
|
||||
state
|
||||
}
|
||||
`;
|
||||
|
||||
type Mutation<Params> = (p: {
|
||||
variables: {
|
||||
input: {
|
||||
measureId: string;
|
||||
} & Params;
|
||||
connections: string[];
|
||||
};
|
||||
}) => void;
|
||||
|
||||
type Props<Params> = {
|
||||
// Measures linked to the element
|
||||
measures: (LinkedMeasuresCardFragment$key & { id: string })[];
|
||||
// Extra params to send to the mutation
|
||||
params: Params;
|
||||
// Disable (action when loading for instance)
|
||||
disabled?: boolean;
|
||||
// ID of the connection to update
|
||||
connectionId: string;
|
||||
// Mutation to attach a measure (will receive {measureId, ...params})
|
||||
onAttach: Mutation<Params>;
|
||||
// Mutation to detach a measure (will receive {measureId, ...params})
|
||||
onDetach: Mutation<Params>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reusable component that displays a list of linked measures
|
||||
*/
|
||||
export function LinkedMeasuresCard<Params>(props: Props<Params>) {
|
||||
const { __ } = useTranslate();
|
||||
const [limit, setLimit] = useState<number | null>(4);
|
||||
const measures = useMemo(() => {
|
||||
return limit ? props.measures.slice(0, limit) : props.measures;
|
||||
}, [props.measures, limit]);
|
||||
const showMoreButton = limit !== null && props.measures.length > limit;
|
||||
|
||||
const onAttach = (measureId: string) => {
|
||||
props.onAttach({
|
||||
variables: {
|
||||
input: {
|
||||
measureId,
|
||||
...props.params,
|
||||
},
|
||||
connections: [props.connectionId],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const onDetach = (measureId: string) => {
|
||||
props.onDetach({
|
||||
variables: {
|
||||
input: {
|
||||
measureId,
|
||||
...props.params,
|
||||
},
|
||||
connections: [props.connectionId],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card padded className="space-y-[10px]">
|
||||
<div className="flex justify-between">
|
||||
<div className="text-lg font-semibold">{__("Measures")}</div>
|
||||
<MeasureLinkDialog
|
||||
connectionId={props.connectionId}
|
||||
disabled={props.disabled}
|
||||
linkedMeasures={props.measures}
|
||||
onLink={onAttach}
|
||||
onUnlink={onDetach}
|
||||
>
|
||||
<Button variant="tertiary" icon={IconPlusLarge}>
|
||||
{__("Link measure")}
|
||||
</Button>
|
||||
</MeasureLinkDialog>
|
||||
</div>
|
||||
{measures.length > 0 ? (
|
||||
<Table className="bg-invert">
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{__("Name")}</Th>
|
||||
<Th>{__("State")}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{measures.map((measure) => (
|
||||
<MeasureRow
|
||||
key={measure.id}
|
||||
measure={measure}
|
||||
onClick={onDetach}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
) : (
|
||||
<div className="text-center text-sm text-txt-secondary">
|
||||
{__("No measures linked")}
|
||||
</div>
|
||||
)}
|
||||
{showMoreButton && (
|
||||
<Button
|
||||
variant="tertiary"
|
||||
onClick={() => setLimit(null)}
|
||||
className="mt-3 mx-auto"
|
||||
icon={IconChevronDown}
|
||||
>
|
||||
{sprintf(__("Show %s more"), props.measures.length - limit)}
|
||||
</Button>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
function MeasureRow(props: {
|
||||
measure: LinkedMeasuresCardFragment$key & { id: string };
|
||||
onClick: (measureId: string) => void;
|
||||
}) {
|
||||
const measure = useFragment(linkedMeasureFragment, props.measure);
|
||||
const organizationId = useOrganizationId();
|
||||
const { __ } = useTranslate();
|
||||
|
||||
return (
|
||||
<Tr to={`/organizations/${organizationId}/measures/${measure.id}`}>
|
||||
<Td>{measure.name}</Td>
|
||||
<Td>
|
||||
<MeasureBadge state={measure.state} />
|
||||
</Td>
|
||||
<Td noLink width={50} className="text-end">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => props.onClick(measure.id)}
|
||||
icon={IconTrashCan}
|
||||
>
|
||||
{__("Unlink")}
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
}
|
||||
166
apps/console2/src/components/measures/MeasureLinkDialog.tsx
Normal file
166
apps/console2/src/components/measures/MeasureLinkDialog.tsx
Normal file
@@ -0,0 +1,166 @@
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
IconMagnifyingGlass,
|
||||
IconPlusLarge,
|
||||
IconTrashCan,
|
||||
Input,
|
||||
Option,
|
||||
Select,
|
||||
Spinner,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Suspense, useMemo, useState, type ReactNode } from "react";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { useLazyLoadQuery } from "react-relay";
|
||||
import type { MeasureLinkDialogQuery } from "./__generated__/MeasureLinkDialogQuery.graphql";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
|
||||
const measuresQuery = graphql`
|
||||
query MeasureLinkDialogQuery($organizationId: ID!) {
|
||||
organization: node(id: $organizationId) {
|
||||
id
|
||||
... on Organization {
|
||||
measures(first: 100) @connection(key: "Organization__measures") {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
state
|
||||
description
|
||||
category
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
connectionId: string;
|
||||
disabled?: boolean;
|
||||
linkedMeasures?: { id: string }[];
|
||||
onLink: (measureId: string) => void;
|
||||
onUnlink: (measureId: string) => void;
|
||||
};
|
||||
|
||||
export function MeasureLinkDialog({ children, ...props }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
|
||||
return (
|
||||
<Dialog trigger={children} title={__("Link measures")}>
|
||||
<DialogContent>
|
||||
<Suspense fallback={<Spinner centered />}>
|
||||
<MeasureLinkDialogContent {...props} />
|
||||
</Suspense>
|
||||
</DialogContent>
|
||||
<DialogFooter exitLabel={__("Close")} />
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function MeasureLinkDialogContent(props: Omit<Props, "children">) {
|
||||
const organizationId = useOrganizationId();
|
||||
const data = useLazyLoadQuery<MeasureLinkDialogQuery>(measuresQuery, {
|
||||
organizationId,
|
||||
});
|
||||
const { __ } = useTranslate();
|
||||
const [search, setSearch] = useState("");
|
||||
const [category, setCategory] = useState<string | null>(null);
|
||||
const measures =
|
||||
data.organization?.measures?.edges?.map((edge) => edge.node) ?? [];
|
||||
const linkedIds = useMemo(() => {
|
||||
return new Set(props.linkedMeasures?.map((m) => m.id) ?? []);
|
||||
}, [props.linkedMeasures]);
|
||||
|
||||
const filteredMeasures = useMemo(() => {
|
||||
return measures.filter(
|
||||
(measure) =>
|
||||
(category === null || measure.category === category) &&
|
||||
(measure.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
measure.description?.toLowerCase().includes(search.toLowerCase()))
|
||||
);
|
||||
}, [measures, search, category]);
|
||||
|
||||
const categories = useMemo(
|
||||
() => Array.from(new Set(measures.map((m) => m.category))),
|
||||
[measures]
|
||||
);
|
||||
|
||||
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 measures...")}
|
||||
onValueChange={setSearch}
|
||||
/>
|
||||
<Select
|
||||
value={category ?? ""}
|
||||
placeholder={__("All categories")}
|
||||
onValueChange={setCategory}
|
||||
className="max-w-[180px]"
|
||||
>
|
||||
{categories.map((category) => (
|
||||
<Option key={category} value={category}>
|
||||
{category}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<div className="divide-y divide-border-low">
|
||||
{filteredMeasures.map((measure) => (
|
||||
<MeasureRow
|
||||
key={measure.id}
|
||||
measure={measure}
|
||||
linkedMeasures={linkedIds}
|
||||
onLink={props.onLink}
|
||||
onUnlink={props.onUnlink}
|
||||
disabled={props.disabled}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type RowProps = {
|
||||
measure: { name: string; category: string; id: string };
|
||||
linkedMeasures: Set<string>;
|
||||
disabled?: boolean;
|
||||
onLink: (measureId: string) => void;
|
||||
onUnlink: (measureId: string) => void;
|
||||
};
|
||||
|
||||
function MeasureRow(props: RowProps) {
|
||||
const { __ } = useTranslate();
|
||||
|
||||
const isLinked = props.linkedMeasures.has(props.measure.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.measure.id)}
|
||||
>
|
||||
{props.measure.name}
|
||||
<Badge variant="neutral">{props.measure.category}</Badge>
|
||||
<Button
|
||||
disabled={props.disabled}
|
||||
className="ml-auto"
|
||||
variant={isLinked ? "secondary" : "primary"}
|
||||
asChild
|
||||
>
|
||||
<span>
|
||||
<IconComponent size={16} /> {isLinked ? __("Unlink") : __("Link")}
|
||||
</span>
|
||||
</Button>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
59
apps/console2/src/components/measures/__generated__/LinkedMeasuresCardFragment.graphql.ts
generated
Normal file
59
apps/console2/src/components/measures/__generated__/LinkedMeasuresCardFragment.graphql.ts
generated
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @generated SignedSource<<b7482b43ce0f4dfab470c35e806acf50>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
export type MeasureState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED";
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type LinkedMeasuresCardFragment$data = {
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly state: MeasureState;
|
||||
readonly " $fragmentType": "LinkedMeasuresCardFragment";
|
||||
};
|
||||
export type LinkedMeasuresCardFragment$key = {
|
||||
readonly " $data"?: LinkedMeasuresCardFragment$data;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"LinkedMeasuresCardFragment">;
|
||||
};
|
||||
|
||||
const node: ReaderFragment = {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "LinkedMeasuresCardFragment",
|
||||
"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": "state",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Measure",
|
||||
"abstractKey": null
|
||||
};
|
||||
|
||||
(node as any).hash = "741a216c02732c1ff97b265ac8dbf39b";
|
||||
|
||||
export default node;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<6a9b2c4428ed2255dda81771894c0c6c>>
|
||||
* @generated SignedSource<<0b725ea8709fbf5da3f17545fcd2b8c4>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -88,6 +88,13 @@ v4 = [
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -102,13 +109,6 @@ v4 = [
|
||||
"name": "category",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
@@ -244,7 +244,7 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "13eb493b76ecc90bc9dd27963b5dba84",
|
||||
"cacheID": "54fff8a0c07ebe02ef34f3bb35e7582e",
|
||||
"id": null,
|
||||
"metadata": {
|
||||
"connection": [
|
||||
@@ -261,11 +261,11 @@ return {
|
||||
},
|
||||
"name": "MeasureLinkDialogQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query MeasureLinkDialogQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n measures(first: 100) {\n edges {\n node {\n id\n name\n description\n category\n state\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||
"text": "query MeasureLinkDialogQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n measures(first: 100) {\n edges {\n node {\n id\n name\n state\n description\n category\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "468d35ade53ae7655cd0fbab242b7d1a";
|
||||
(node as any).hash = "cf01fa3a766badd4f6150e1a544403ba";
|
||||
|
||||
export default node;
|
||||
@@ -1,48 +1,4 @@
|
||||
import {
|
||||
Badge,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
IconMagnifyingGlass,
|
||||
IconPlusLarge,
|
||||
IconTrashCan,
|
||||
Input,
|
||||
Option,
|
||||
Select,
|
||||
Spinner,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { Suspense, useMemo, useState, type ReactNode } from "react";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { useLazyLoadQuery, useMutation } from "react-relay";
|
||||
import type {
|
||||
MeasureLinkDialogQuery,
|
||||
MeasureLinkDialogQuery$data,
|
||||
} from "./__generated__/MeasureLinkDialogQuery.graphql";
|
||||
import type { NodeOf } from "/types";
|
||||
|
||||
const measuresQuery = graphql`
|
||||
query MeasureLinkDialogQuery($organizationId: ID!) {
|
||||
organization: node(id: $organizationId) {
|
||||
id
|
||||
... on Organization {
|
||||
measures(first: 100) @connection(key: "Organization__measures") {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
description
|
||||
category
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/*
|
||||
const attachMeasureMutation = graphql`
|
||||
mutation MeasureLinkDialogCreateMutation(
|
||||
$input: CreateRiskMeasureMappingInput!
|
||||
@@ -72,132 +28,4 @@ export const detachMeasureMutation = graphql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
trigger: ReactNode;
|
||||
organizationId: string;
|
||||
connectionId: string;
|
||||
riskId: string;
|
||||
linkedIds?: Set<string>;
|
||||
};
|
||||
|
||||
export function MeasureLinkDialog({ trigger, ...props }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
|
||||
return (
|
||||
<Dialog trigger={trigger} title={__("Manage Risk Measures")}>
|
||||
<DialogContent className="px-6">
|
||||
<p className="text-sm text-txt-secondary mt-6">
|
||||
{__("Link or unlink measures to manage this risk.")}
|
||||
</p>
|
||||
<Suspense fallback={<Spinner centered />}>
|
||||
<MeasureLinkDialogContent {...props} />
|
||||
</Suspense>
|
||||
</DialogContent>
|
||||
<DialogFooter exitLabel={__("Close")} />
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function MeasureLinkDialogContent(props: Omit<Props, "trigger">) {
|
||||
const data = useLazyLoadQuery<MeasureLinkDialogQuery>(measuresQuery, {
|
||||
organizationId: props.organizationId,
|
||||
});
|
||||
const { __ } = useTranslate();
|
||||
const [search, setSearch] = useState("");
|
||||
const [category, setCategory] = useState<string | null>(null);
|
||||
const measures =
|
||||
data.organization?.measures?.edges?.map((edge) => edge.node) ?? [];
|
||||
|
||||
const filteredMeasures = useMemo(() => {
|
||||
return measures.filter(
|
||||
(measure) =>
|
||||
(category === null || measure.category === category) &&
|
||||
(measure.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
measure.description?.toLowerCase().includes(search.toLowerCase()))
|
||||
);
|
||||
}, [measures, search, category]);
|
||||
|
||||
const categories = useMemo(
|
||||
() => Array.from(new Set(measures.map((m) => m.category))),
|
||||
[measures]
|
||||
);
|
||||
|
||||
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">
|
||||
<Input
|
||||
icon={IconMagnifyingGlass}
|
||||
placeholder={__("Search measures...")}
|
||||
onValueChange={setSearch}
|
||||
/>
|
||||
<Select
|
||||
value={category ?? ""}
|
||||
placeholder={__("All categories")}
|
||||
onValueChange={setCategory}
|
||||
className="max-w-[180px]"
|
||||
>
|
||||
{categories.map((category) => (
|
||||
<Option key={category} value={category}>
|
||||
{category}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
<div className="divide-y divide-border-low">
|
||||
{filteredMeasures.map((measure) => (
|
||||
<MeasureRow key={measure.id} measure={measure} {...props} />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
type RowProps = {
|
||||
measure: NodeOf<
|
||||
Required<MeasureLinkDialogQuery$data["organization"]>["measures"]
|
||||
>;
|
||||
} & Omit<Props, "trigger">;
|
||||
|
||||
function MeasureRow(props: RowProps) {
|
||||
const isLinked = props.linkedIds?.has(props.measure.id) ?? false;
|
||||
const { __ } = useTranslate();
|
||||
const [attachMeasure, isFetchingAttach] = useMutation(attachMeasureMutation);
|
||||
const [detachMeasure, isFetchingDetach] = useMutation(detachMeasureMutation);
|
||||
|
||||
const isFetching = isFetchingAttach || isFetchingDetach;
|
||||
|
||||
const onClick = () => {
|
||||
if (isFetching) {
|
||||
return;
|
||||
}
|
||||
const action = isLinked ? detachMeasure : attachMeasure;
|
||||
action({
|
||||
variables: {
|
||||
input: {
|
||||
riskId: props.riskId,
|
||||
measureId: props.measure.id,
|
||||
},
|
||||
connections: [props.connectionId],
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="py-4 flex items-center gap-4 hover:bg-subtle cursor-pointer"
|
||||
onClick={onClick}
|
||||
>
|
||||
{props.measure.name}
|
||||
<Badge variant="neutral">{props.measure.category}</Badge>
|
||||
<Button
|
||||
disabled={isFetching}
|
||||
icon={isLinked ? IconTrashCan : IconPlusLarge}
|
||||
className="ml-auto"
|
||||
variant={isLinked ? "secondary" : "primary"}
|
||||
>
|
||||
{isLinked ? __("Unlink") : __("Link")}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
/**
|
||||
* @generated SignedSource<<043e8cc5beda1ac8949a7c02adcff4bf>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type MeasureState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED";
|
||||
export type CreateRiskMeasureMappingInput = {
|
||||
measureId: string;
|
||||
riskId: string;
|
||||
};
|
||||
export type MeasureLinkDialogCreateMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: CreateRiskMeasureMappingInput;
|
||||
};
|
||||
export type MeasureLinkDialogCreateMutation$data = {
|
||||
readonly createRiskMeasureMapping: {
|
||||
readonly measureEdge: {
|
||||
readonly node: {
|
||||
readonly category: string;
|
||||
readonly description: string;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly state: MeasureState;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
export type MeasureLinkDialogCreateMutation = {
|
||||
response: MeasureLinkDialogCreateMutation$data;
|
||||
variables: MeasureLinkDialogCreateMutation$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,
|
||||
"concreteType": "MeasureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "measureEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Measure",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"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": "category",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [
|
||||
(v0/*: any*/),
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "MeasureLinkDialogCreateMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateRiskMeasureMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createRiskMeasureMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "MeasureLinkDialogCreateMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateRiskMeasureMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createRiskMeasureMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "prependEdge",
|
||||
"key": "",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "measureEdge",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "309f3c90d91577e2f1c95ea69848ce5a",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "MeasureLinkDialogCreateMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation MeasureLinkDialogCreateMutation(\n $input: CreateRiskMeasureMappingInput!\n) {\n createRiskMeasureMapping(input: $input) {\n measureEdge {\n node {\n id\n name\n description\n category\n state\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "bd012f06e39d5a04f4c1a2d2026f497e";
|
||||
|
||||
export default node;
|
||||
@@ -70,3 +70,15 @@ export const useDeleteFrameworkMutation = (
|
||||
);
|
||||
}, [framework, connectionId, commitDelete]);
|
||||
};
|
||||
|
||||
export const frameworkNodeQuery = graphql`
|
||||
query FrameworkGraphNodeQuery($frameworkId: ID!) {
|
||||
node(id: $frameworkId) {
|
||||
... on Framework {
|
||||
id
|
||||
name
|
||||
...FrameworkDetailPageFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
407
apps/console2/src/hooks/graph/__generated__/FrameworkGraphNodeQuery.graphql.ts
generated
Normal file
407
apps/console2/src/hooks/graph/__generated__/FrameworkGraphNodeQuery.graphql.ts
generated
Normal file
@@ -0,0 +1,407 @@
|
||||
/**
|
||||
* @generated SignedSource<<64acca659bff60ca637937d933fc839f>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type FrameworkGraphNodeQuery$variables = {
|
||||
frameworkId: string;
|
||||
};
|
||||
export type FrameworkGraphNodeQuery$data = {
|
||||
readonly node: {
|
||||
readonly id?: string;
|
||||
readonly name?: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"FrameworkDetailPageFragment">;
|
||||
};
|
||||
};
|
||||
export type FrameworkGraphNodeQuery = {
|
||||
response: FrameworkGraphNodeQuery$data;
|
||||
variables: FrameworkGraphNodeQuery$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
var v0 = [
|
||||
{
|
||||
"defaultValue": null,
|
||||
"kind": "LocalArgument",
|
||||
"name": "frameworkId"
|
||||
}
|
||||
],
|
||||
v1 = [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "id",
|
||||
"variableName": "frameworkId"
|
||||
}
|
||||
],
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v5 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
},
|
||||
v6 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 100
|
||||
}
|
||||
],
|
||||
v7 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "cursor",
|
||||
"storageKey": null
|
||||
},
|
||||
v8 = {
|
||||
"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
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
v9 = {
|
||||
"kind": "ClientExtension",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__id",
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "FrameworkGraphNodeQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "FrameworkDetailPageFragment"
|
||||
}
|
||||
],
|
||||
"type": "Framework",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Query",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": (v0/*: any*/),
|
||||
"kind": "Operation",
|
||||
"name": "FrameworkGraphNodeQuery",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v1/*: any*/),
|
||||
"concreteType": null,
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v4/*: any*/),
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"kind": "InlineFragment",
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
(v5/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v6/*: any*/),
|
||||
"concreteType": "ControlConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "controls",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "ControlEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Control",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "referenceId",
|
||||
"storageKey": null
|
||||
},
|
||||
(v3/*: any*/),
|
||||
(v5/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v6/*: any*/),
|
||||
"concreteType": "MeasureConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "measures",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "MeasureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Measure",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
},
|
||||
(v4/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v7/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v8/*: any*/),
|
||||
(v9/*: any*/)
|
||||
],
|
||||
"storageKey": "measures(first:100)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v6/*: any*/),
|
||||
"filters": null,
|
||||
"handle": "connection",
|
||||
"key": "FrameworkDetailPage_measures",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "measures"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v6/*: any*/),
|
||||
"concreteType": "DocumentConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "documents",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Document",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 1
|
||||
}
|
||||
],
|
||||
"concreteType": "DocumentVersionConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "versions",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentVersionEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentVersion",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v2/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "versions(first:1)"
|
||||
},
|
||||
(v4/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v7/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v8/*: any*/),
|
||||
(v9/*: any*/)
|
||||
],
|
||||
"storageKey": "documents(first:100)"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v6/*: any*/),
|
||||
"filters": null,
|
||||
"handle": "connection",
|
||||
"key": "FrameworkDetailPage_documents",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "documents"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "controls(first:100)"
|
||||
}
|
||||
],
|
||||
"type": "Framework",
|
||||
"abstractKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "b10b304da4ff5b00e15b881ee486e8fe",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "FrameworkGraphNodeQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query FrameworkGraphNodeQuery(\n $frameworkId: ID!\n) {\n node(id: $frameworkId) {\n __typename\n ... on Framework {\n id\n name\n ...FrameworkDetailPageFragment\n }\n id\n }\n}\n\nfragment FrameworkDetailPageFragment on Framework {\n id\n name\n description\n controls(first: 100) {\n edges {\n node {\n id\n referenceId\n name\n description\n measures(first: 100) {\n edges {\n node {\n id\n ...LinkedMeasuresCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n documents(first: 100) {\n edges {\n node {\n id\n ...LinkedDocumentsCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n }\n}\n\nfragment LinkedDocumentsCardFragment on Document {\n id\n title\n createdAt\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "69a32dc8fbcaefbbaeb02dd2b89752c4";
|
||||
|
||||
export default node;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<18341e1d2a7f480ad73ef32506121c5e>>
|
||||
* @generated SignedSource<<dfb9e4b341ccc895fce60e2de5287ec1>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -108,14 +108,7 @@ v8 = {
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v9 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "category",
|
||||
"storageKey": null
|
||||
},
|
||||
v10 = [
|
||||
v9 = [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
@@ -195,7 +188,13 @@ return {
|
||||
(v4/*: any*/),
|
||||
(v6/*: any*/),
|
||||
(v7/*: any*/),
|
||||
(v9/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "category",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -240,7 +239,7 @@ return {
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v10/*: any*/),
|
||||
"args": (v9/*: any*/),
|
||||
"concreteType": "MeasureConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "measures",
|
||||
@@ -264,15 +263,6 @@ return {
|
||||
"selections": [
|
||||
(v5/*: any*/),
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/),
|
||||
(v9/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
@@ -336,7 +326,7 @@ return {
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v10/*: any*/),
|
||||
"args": (v9/*: any*/),
|
||||
"filters": null,
|
||||
"handle": "connection",
|
||||
"key": "Risk__measures",
|
||||
@@ -353,12 +343,12 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "fc9da3522dfacf8b5f887a0ebbd3e21e",
|
||||
"cacheID": "295ecc0e99f46b7790fe04db0deed1d8",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "RiskGraphNodeQuery",
|
||||
"operationKind": "query",
|
||||
"text": "query RiskGraphNodeQuery(\n $riskId: ID!\n) {\n node(id: $riskId) {\n __typename\n ... on Risk {\n name\n description\n treatment\n owner {\n id\n fullName\n }\n note\n ...useRiskFormFragment\n ...RiskOverviewTabFragment\n ...RiskMeasuresTabFragment\n }\n id\n }\n}\n\nfragment RiskMeasuresTabFragment on Risk {\n id\n measures(first: 100) {\n edges {\n node {\n id\n name\n description\n category\n createdAt\n state\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment RiskOverviewTabFragment on Risk {\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n inherentRiskScore\n residualRiskScore\n}\n\nfragment useRiskFormFragment on Risk {\n id\n name\n category\n description\n treatment\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n note\n owner {\n id\n }\n}\n"
|
||||
"text": "query RiskGraphNodeQuery(\n $riskId: ID!\n) {\n node(id: $riskId) {\n __typename\n ... on Risk {\n name\n description\n treatment\n owner {\n id\n fullName\n }\n note\n ...useRiskFormFragment\n ...RiskOverviewTabFragment\n ...RiskMeasuresTabFragment\n }\n id\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n\nfragment RiskMeasuresTabFragment on Risk {\n id\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}\n\nfragment RiskOverviewTabFragment on Risk {\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n inherentRiskScore\n residualRiskScore\n}\n\nfragment useRiskFormFragment on Risk {\n id\n name\n category\n description\n treatment\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n note\n owner {\n id\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
import {
|
||||
useFragment,
|
||||
useMutation,
|
||||
usePreloadedQuery,
|
||||
type PreloadedQuery,
|
||||
} from "react-relay";
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { graphql } from "relay-runtime";
|
||||
import { ControlItem, PageHeader } from "@probo/ui";
|
||||
import { FrameworkLogo } from "/components/FrameworkLogo";
|
||||
import { frameworkNodeQuery } from "/hooks/graph/FrameworkGraph";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { LinkedMeasuresCard } from "/components/measures/LinkedMeasuresCard";
|
||||
import { useParams } from "react-router";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import type { FrameworkGraphNodeQuery } from "/hooks/graph/__generated__/FrameworkGraphNodeQuery.graphql";
|
||||
import type { FrameworkDetailPageFragment$key } from "./__generated__/FrameworkDetailPageFragment.graphql";
|
||||
import { LinkedDocumentsCard } from "/components/documents/LinkedDocumentsCard";
|
||||
|
||||
const frameworkDetailFragment = graphql`
|
||||
fragment FrameworkDetailPageFragment on Framework {
|
||||
id
|
||||
name
|
||||
description
|
||||
controls(first: 100) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
referenceId
|
||||
name
|
||||
description
|
||||
measures(first: 100)
|
||||
@connection(key: "FrameworkDetailPage_measures") {
|
||||
__id
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...LinkedMeasuresCardFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
documents(first: 100)
|
||||
@connection(key: "FrameworkDetailPage_documents") {
|
||||
__id
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...LinkedDocumentsCardFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const attachMeasureMutation = graphql`
|
||||
mutation FrameworkDetailPageAttachMutation(
|
||||
$input: CreateControlMeasureMappingInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
createControlMeasureMapping(input: $input) {
|
||||
measureEdge @prependEdge(connections: $connections) {
|
||||
node {
|
||||
id
|
||||
...LinkedMeasuresCardFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const detachMeasureMutation = graphql`
|
||||
mutation FrameworkDetailPageDetachMutation(
|
||||
$input: DeleteControlMeasureMappingInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
deleteControlMeasureMapping(input: $input) {
|
||||
deletedMeasureId @deleteEdge(connections: $connections)
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const attachDocumentMutation = graphql`
|
||||
mutation FrameworkDetailPageAttachDocumentMutation(
|
||||
$input: CreateControlDocumentMappingInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
createControlDocumentMapping(input: $input) {
|
||||
documentEdge @prependEdge(connections: $connections) {
|
||||
node {
|
||||
id
|
||||
...LinkedDocumentsCardFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const detachDocumentMutation = graphql`
|
||||
mutation FrameworkDetailPageDetachDocumentMutation(
|
||||
$input: DeleteControlDocumentMappingInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
deleteControlDocumentMapping(input: $input) {
|
||||
deletedDocumentId @deleteEdge(connections: $connections)
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
queryRef: PreloadedQuery<FrameworkGraphNodeQuery>;
|
||||
};
|
||||
|
||||
export default function FrameworkDetailPage(props: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { controlId } = useParams<{ controlId?: string }>();
|
||||
const organizationId = useOrganizationId();
|
||||
const data = usePreloadedQuery(frameworkNodeQuery, props.queryRef);
|
||||
const framework = useFragment<FrameworkDetailPageFragment$key>(
|
||||
frameworkDetailFragment,
|
||||
data.node
|
||||
);
|
||||
|
||||
const [detachMeasure, isDetachingMeasure] = useMutation(
|
||||
detachMeasureMutation
|
||||
);
|
||||
const [attachMeasure, isAttachingMeasure] = useMutation(
|
||||
attachMeasureMutation
|
||||
);
|
||||
const [detachDocument, isDetachingDocument] = useMutation(
|
||||
detachDocumentMutation
|
||||
);
|
||||
const [attachDocument, isAttachingDocument] = useMutation(
|
||||
attachDocumentMutation
|
||||
);
|
||||
|
||||
const controls = framework.controls.edges.map((edge) => edge.node);
|
||||
|
||||
const selectedControl = controlId
|
||||
? controls.find((control) => control.id === controlId)
|
||||
: controls[0];
|
||||
usePageTitle(`${framework.name} | ${selectedControl?.referenceId}`);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<PageHeader
|
||||
title={
|
||||
<>
|
||||
<FrameworkLogo {...framework} />
|
||||
{framework.name}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<div className="text-lg font-semibold">
|
||||
{__("Requirement categories")}
|
||||
</div>
|
||||
<div className="divide-x divide-border-low grid grid-cols-[264px_1fr]">
|
||||
<div
|
||||
className="space-y-1 overflow-y-auto pr-6 mr-6 sticky top-0"
|
||||
style={{ maxHeight: "calc(100vh - 48px)" }}
|
||||
>
|
||||
{controls.map((control) => (
|
||||
<ControlItem
|
||||
key={control.id}
|
||||
id={control.referenceId}
|
||||
description={control.name ?? control.description}
|
||||
to={`/organizations/${organizationId}/frameworks/${framework.id}/${control.id}`}
|
||||
active={selectedControl?.id === control.id}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{selectedControl ? (
|
||||
<div className="space-y-6">
|
||||
<div className="text-xl font-medium px-[6px] py-[2px] border border-border-low rounded-lg w-max bg-active mb-3">
|
||||
{selectedControl.referenceId}
|
||||
</div>
|
||||
<div className="text-base">{selectedControl.name}</div>
|
||||
<LinkedMeasuresCard
|
||||
measures={
|
||||
selectedControl?.measures.edges.map((edge) => edge.node) ?? []
|
||||
}
|
||||
params={{ controlId: selectedControl.id }}
|
||||
connectionId={selectedControl.measures.__id!}
|
||||
onAttach={attachMeasure}
|
||||
onDetach={detachMeasure}
|
||||
disabled={isAttachingMeasure || isDetachingMeasure}
|
||||
/>
|
||||
<LinkedDocumentsCard
|
||||
documents={
|
||||
selectedControl?.documents.edges.map((edge) => edge.node) ?? []
|
||||
}
|
||||
params={{ controlId: selectedControl.id }}
|
||||
connectionId={selectedControl.documents.__id!}
|
||||
onAttach={attachDocument}
|
||||
onDetach={detachDocument}
|
||||
disabled={isAttachingDocument || isDetachingDocument}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-full flex items-center justify-center">
|
||||
<div className="text-sm text-txt-secondary">
|
||||
{__("No control selected")}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { usePageTitle } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import {
|
||||
ActionDropdown,
|
||||
Avatar,
|
||||
Card,
|
||||
DropdownItem,
|
||||
FileButton,
|
||||
@@ -27,8 +26,8 @@ import { Link } from "react-router";
|
||||
import type { FrameworksPageCardFragment$key } from "./__generated__/FrameworksPageCardFragment.graphql";
|
||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||
import { useState, type ChangeEventHandler } from "react";
|
||||
import { availableFrameworks } from "@probo/helpers";
|
||||
import { CreateFrameworkDialog } from "./dialogs/CreateFrameworkDialog";
|
||||
import { FrameworkLogo } from "/components/FrameworkLogo";
|
||||
|
||||
type Props = {
|
||||
queryRef: PreloadedQuery<FrameworkGraphListQuery>;
|
||||
@@ -50,10 +49,6 @@ const importFrameworkMutation = graphql`
|
||||
}
|
||||
`;
|
||||
|
||||
const availableLogos = new Map(
|
||||
availableFrameworks.map((framework) => [framework.name, framework.logo])
|
||||
);
|
||||
|
||||
export default function FrameworksPage(props: Props) {
|
||||
const { __ } = useTranslate();
|
||||
usePageTitle(__("Frameworks"));
|
||||
@@ -182,15 +177,10 @@ function FrameworkCard(props: FrameworkCardProps) {
|
||||
props.connectionId
|
||||
);
|
||||
const { __ } = useTranslate();
|
||||
const logo = availableLogos.get(framework.name);
|
||||
return (
|
||||
<Card padded className="p-6 bg-white rounded shadow relative">
|
||||
<div className="flex justify-between mb-3">
|
||||
{logo ? (
|
||||
<img src={logo} alt="" className="size-12" />
|
||||
) : (
|
||||
<Avatar name={framework.name} size="l" className="size-12" />
|
||||
)}
|
||||
<FrameworkLogo {...framework} />
|
||||
<ActionDropdown className="z-10 relative">
|
||||
<DropdownItem
|
||||
icon={IconTrashCan}
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* @generated SignedSource<<174d7c77d0d858ecc59261174b424af2>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type CreateControlDocumentMappingInput = {
|
||||
controlId: string;
|
||||
documentId: string;
|
||||
};
|
||||
export type FrameworkDetailPageAttachDocumentMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: CreateControlDocumentMappingInput;
|
||||
};
|
||||
export type FrameworkDetailPageAttachDocumentMutation$data = {
|
||||
readonly createControlDocumentMapping: {
|
||||
readonly documentEdge: {
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"LinkedDocumentsCardFragment">;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
export type FrameworkDetailPageAttachDocumentMutation = {
|
||||
response: FrameworkDetailPageAttachDocumentMutation$data;
|
||||
variables: FrameworkDetailPageAttachDocumentMutation$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": "FrameworkDetailPageAttachDocumentMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateControlDocumentMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createControlDocumentMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "documentEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Document",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "LinkedDocumentsCardFragment"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "FrameworkDetailPageAttachDocumentMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateControlDocumentMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createControlDocumentMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "documentEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Document",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "title",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 1
|
||||
}
|
||||
],
|
||||
"concreteType": "DocumentVersionConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "versions",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentVersionEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentVersion",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "status",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "versions(first:1)"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "prependEdge",
|
||||
"key": "",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "documentEdge",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "9100dabaa4861920eab52dc47e551b06",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "FrameworkDetailPageAttachDocumentMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation FrameworkDetailPageAttachDocumentMutation(\n $input: CreateControlDocumentMappingInput!\n) {\n createControlDocumentMapping(input: $input) {\n documentEdge {\n node {\n id\n ...LinkedDocumentsCardFragment\n }\n }\n }\n}\n\nfragment LinkedDocumentsCardFragment on Document {\n id\n title\n createdAt\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "26c53db439feba2b2d65c3e3a9a973c7";
|
||||
|
||||
export default node;
|
||||
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* @generated SignedSource<<81d008fc4116a78a92e2d925f8d6daa7>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type CreateControlMeasureMappingInput = {
|
||||
controlId: string;
|
||||
measureId: string;
|
||||
};
|
||||
export type FrameworkDetailPageAttachMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: CreateControlMeasureMappingInput;
|
||||
};
|
||||
export type FrameworkDetailPageAttachMutation$data = {
|
||||
readonly createControlMeasureMapping: {
|
||||
readonly measureEdge: {
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"LinkedMeasuresCardFragment">;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
export type FrameworkDetailPageAttachMutation = {
|
||||
response: FrameworkDetailPageAttachMutation$data;
|
||||
variables: FrameworkDetailPageAttachMutation$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": "FrameworkDetailPageAttachMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateControlMeasureMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createControlMeasureMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "MeasureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "measureEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Measure",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "LinkedMeasuresCardFragment"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "FrameworkDetailPageAttachMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateControlMeasureMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createControlMeasureMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "MeasureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "measureEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Measure",
|
||||
"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": "state",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "prependEdge",
|
||||
"key": "",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "measureEdge",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "9fcb599cd78f3108d6d71aa571d7005d",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "FrameworkDetailPageAttachMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation FrameworkDetailPageAttachMutation(\n $input: CreateControlMeasureMappingInput!\n) {\n createControlMeasureMapping(input: $input) {\n measureEdge {\n node {\n id\n ...LinkedMeasuresCardFragment\n }\n }\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "d3834b8c2656d347e13189469894be61";
|
||||
|
||||
export default node;
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* @generated SignedSource<<6f2850042c0413963bd82f90236fe220>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type DeleteControlDocumentMappingInput = {
|
||||
controlId: string;
|
||||
documentId: string;
|
||||
};
|
||||
export type FrameworkDetailPageDetachDocumentMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: DeleteControlDocumentMappingInput;
|
||||
};
|
||||
export type FrameworkDetailPageDetachDocumentMutation$data = {
|
||||
readonly deleteControlDocumentMapping: {
|
||||
readonly deletedDocumentId: string;
|
||||
};
|
||||
};
|
||||
export type FrameworkDetailPageDetachDocumentMutation = {
|
||||
response: FrameworkDetailPageDetachDocumentMutation$data;
|
||||
variables: FrameworkDetailPageDetachDocumentMutation$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": "deletedDocumentId",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [
|
||||
(v0/*: any*/),
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "FrameworkDetailPageDetachDocumentMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "DeleteControlDocumentMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "deleteControlDocumentMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "FrameworkDetailPageDetachDocumentMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "DeleteControlDocumentMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "deleteControlDocumentMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "deleteEdge",
|
||||
"key": "",
|
||||
"kind": "ScalarHandle",
|
||||
"name": "deletedDocumentId",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "53618baa1678e3a1b496a08597bdd364",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "FrameworkDetailPageDetachDocumentMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation FrameworkDetailPageDetachDocumentMutation(\n $input: DeleteControlDocumentMappingInput!\n) {\n deleteControlDocumentMapping(input: $input) {\n deletedDocumentId\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "233b182104744a67a21d21d60b6df67a";
|
||||
|
||||
export default node;
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* @generated SignedSource<<ff996c0902be9e7b8c23c9dd3d0f3759>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
export type DeleteControlMeasureMappingInput = {
|
||||
controlId: string;
|
||||
measureId: string;
|
||||
};
|
||||
export type FrameworkDetailPageDetachMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: DeleteControlMeasureMappingInput;
|
||||
};
|
||||
export type FrameworkDetailPageDetachMutation$data = {
|
||||
readonly deleteControlMeasureMapping: {
|
||||
readonly deletedMeasureId: string;
|
||||
};
|
||||
};
|
||||
export type FrameworkDetailPageDetachMutation = {
|
||||
response: FrameworkDetailPageDetachMutation$data;
|
||||
variables: FrameworkDetailPageDetachMutation$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": "deletedMeasureId",
|
||||
"storageKey": null
|
||||
};
|
||||
return {
|
||||
"fragment": {
|
||||
"argumentDefinitions": [
|
||||
(v0/*: any*/),
|
||||
(v1/*: any*/)
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "FrameworkDetailPageDetachMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "DeleteControlMeasureMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "deleteControlMeasureMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "FrameworkDetailPageDetachMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "DeleteControlMeasureMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "deleteControlMeasureMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "deleteEdge",
|
||||
"key": "",
|
||||
"kind": "ScalarHandle",
|
||||
"name": "deletedMeasureId",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "0bede5dc29caa4e2aba9b0dd96e58014",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "FrameworkDetailPageDetachMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation FrameworkDetailPageDetachMutation(\n $input: DeleteControlMeasureMappingInput!\n) {\n deleteControlMeasureMapping(input: $input) {\n deletedMeasureId\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "96945bf8e909e7425af2297f7ee6ea0a";
|
||||
|
||||
export default node;
|
||||
288
apps/console2/src/pages/organizations/frameworks/__generated__/FrameworkDetailPageFragment.graphql.ts
generated
Normal file
288
apps/console2/src/pages/organizations/frameworks/__generated__/FrameworkDetailPageFragment.graphql.ts
generated
Normal file
@@ -0,0 +1,288 @@
|
||||
/**
|
||||
* @generated SignedSource<<15cc2290d512a0cdde5241edd045d3f5>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type FrameworkDetailPageFragment$data = {
|
||||
readonly controls: {
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly description: string;
|
||||
readonly documents: {
|
||||
readonly __id: string;
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"LinkedDocumentsCardFragment">;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly id: string;
|
||||
readonly measures: {
|
||||
readonly __id: string;
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"LinkedMeasuresCardFragment">;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly name: string;
|
||||
readonly referenceId: string;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
readonly description: string;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly " $fragmentType": "FrameworkDetailPageFragment";
|
||||
};
|
||||
export type FrameworkDetailPageFragment$key = {
|
||||
readonly " $data"?: FrameworkDetailPageFragment$data;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"FrameworkDetailPageFragment">;
|
||||
};
|
||||
|
||||
const node: ReaderFragment = (function(){
|
||||
var v0 = {
|
||||
"count": null,
|
||||
"cursor": null,
|
||||
"direction": "forward",
|
||||
"path": null
|
||||
},
|
||||
v1 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "id",
|
||||
"storageKey": null
|
||||
},
|
||||
v2 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "name",
|
||||
"storageKey": null
|
||||
},
|
||||
v3 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "description",
|
||||
"storageKey": null
|
||||
},
|
||||
v4 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__typename",
|
||||
"storageKey": null
|
||||
},
|
||||
v5 = {
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "cursor",
|
||||
"storageKey": null
|
||||
},
|
||||
v6 = {
|
||||
"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
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
v7 = {
|
||||
"kind": "ClientExtension",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "__id",
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
};
|
||||
return {
|
||||
"argumentDefinitions": [],
|
||||
"kind": "Fragment",
|
||||
"metadata": {
|
||||
"connection": [
|
||||
(v0/*: any*/),
|
||||
(v0/*: any*/)
|
||||
]
|
||||
},
|
||||
"name": "FrameworkDetailPageFragment",
|
||||
"selections": [
|
||||
(v1/*: any*/),
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": [
|
||||
{
|
||||
"kind": "Literal",
|
||||
"name": "first",
|
||||
"value": 100
|
||||
}
|
||||
],
|
||||
"concreteType": "ControlConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "controls",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "ControlEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Control",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v1/*: any*/),
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "referenceId",
|
||||
"storageKey": null
|
||||
},
|
||||
(v2/*: any*/),
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"alias": "measures",
|
||||
"args": null,
|
||||
"concreteType": "MeasureConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "__FrameworkDetailPage_measures_connection",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "MeasureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Measure",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v1/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "LinkedMeasuresCardFragment"
|
||||
},
|
||||
(v4/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v5/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v6/*: any*/),
|
||||
(v7/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": "documents",
|
||||
"args": null,
|
||||
"concreteType": "DocumentConnection",
|
||||
"kind": "LinkedField",
|
||||
"name": "__FrameworkDetailPage_documents_connection",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "DocumentEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "edges",
|
||||
"plural": true,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Document",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v1/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "LinkedDocumentsCardFragment"
|
||||
},
|
||||
(v4/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v5/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
(v6/*: any*/),
|
||||
(v7/*: any*/)
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": "controls(first:100)"
|
||||
}
|
||||
],
|
||||
"type": "Framework",
|
||||
"abstractKey": null
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "15b523ca06d1a1e1a80379f6d51b92e2";
|
||||
|
||||
export default node;
|
||||
@@ -67,7 +67,6 @@ export function CreateFrameworkDialog(props: Props) {
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
defaultOpen
|
||||
ref={props.ref}
|
||||
title={<Breadcrumb items={[__("Framework"), __("New Framework")]} />}
|
||||
>
|
||||
|
||||
@@ -1,28 +1,7 @@
|
||||
import { graphql, useFragment, useMutation } from "react-relay";
|
||||
import type {
|
||||
RiskMeasuresTabFragment$data,
|
||||
RiskMeasuresTabFragment$key,
|
||||
} from "./__generated__/RiskMeasuresTabFragment.graphql";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import {
|
||||
Button,
|
||||
IconPlusLarge,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Td,
|
||||
Th,
|
||||
Tr,
|
||||
IconTrashCan,
|
||||
} from "@probo/ui";
|
||||
import {
|
||||
detachMeasureMutation,
|
||||
MeasureLinkDialog,
|
||||
} from "/components/risks/MeasureLinkDialog";
|
||||
import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import type { RiskMeasuresTabFragment$key } from "./__generated__/RiskMeasuresTabFragment.graphql";
|
||||
import { useOutletContext } from "react-router";
|
||||
import type { NodeOf } from "/types";
|
||||
import { useMemo } from "react";
|
||||
import { LinkedMeasuresCard } from "/components/measures/LinkedMeasuresCard";
|
||||
|
||||
const measuresFragment = graphql`
|
||||
fragment RiskMeasuresTabFragment on Risk {
|
||||
@@ -32,127 +11,60 @@ const measuresFragment = graphql`
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
description
|
||||
category
|
||||
createdAt
|
||||
state
|
||||
...LinkedMeasuresCardFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const attachMeasureMutation = graphql`
|
||||
mutation RiskMeasuresTabCreateMutation(
|
||||
$input: CreateRiskMeasureMappingInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
createRiskMeasureMapping(input: $input) {
|
||||
measureEdge @prependEdge(connections: $connections) {
|
||||
node {
|
||||
id
|
||||
...LinkedMeasuresCardFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const detachMeasureMutation = graphql`
|
||||
mutation RiskMeasuresTabDetachMutation(
|
||||
$input: DeleteRiskMeasureMappingInput!
|
||||
$connections: [ID!]!
|
||||
) {
|
||||
deleteRiskMeasureMapping(input: $input) {
|
||||
deletedMeasureId @deleteEdge(connections: $connections)
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default function RiskMeasuresTab() {
|
||||
const { risk } = useOutletContext<{
|
||||
risk: RiskMeasuresTabFragment$key & { id: string };
|
||||
}>();
|
||||
const data = useFragment(measuresFragment, risk);
|
||||
const organizationId = useOrganizationId();
|
||||
const { __ } = useTranslate();
|
||||
const connectionId = data.measures.__id;
|
||||
const measures = data.measures?.edges?.map((edge) => edge.node) ?? [];
|
||||
const linkedIds = useMemo(
|
||||
() => new Set(measures.map((measure) => measure.id)),
|
||||
[measures]
|
||||
);
|
||||
|
||||
if (measures.length === 0) {
|
||||
return (
|
||||
<div className="text-sm text-txt-secondary text-center flex flex-col gap-4 items-center justify-center py-10">
|
||||
{__("No measures associated with this risk.")}
|
||||
<MeasureLinkDialog
|
||||
linkedIds={linkedIds}
|
||||
connectionId={connectionId}
|
||||
organizationId={organizationId}
|
||||
riskId={data.id}
|
||||
trigger={
|
||||
<Button icon={IconPlusLarge} variant="quaternary">
|
||||
{__("Link measure")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const [detachMeasure, isDetaching] = useMutation(detachMeasureMutation);
|
||||
const [attachMeasure, isAttaching] = useMutation(attachMeasureMutation);
|
||||
const isLoading = isDetaching || isAttaching;
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<MeasureLinkDialog
|
||||
linkedIds={linkedIds}
|
||||
connectionId={connectionId}
|
||||
organizationId={organizationId}
|
||||
riskId={data.id}
|
||||
trigger={
|
||||
<Button
|
||||
icon={IconPlusLarge}
|
||||
variant="primary"
|
||||
className="absolute -top-18 right-0"
|
||||
>
|
||||
{__("Link measure")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{__("Measure")}</Th>
|
||||
<Th></Th>
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{measures.map((measure) => (
|
||||
<MeasureRow
|
||||
key={measure.id}
|
||||
measure={measure}
|
||||
riskId={data.id}
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function MeasureRow({
|
||||
measure,
|
||||
riskId,
|
||||
connectionId,
|
||||
}: {
|
||||
measure: NodeOf<RiskMeasuresTabFragment$data["measures"]>;
|
||||
riskId: string;
|
||||
connectionId: string;
|
||||
}) {
|
||||
const { __ } = useTranslate();
|
||||
|
||||
const [detachMeasure, isFetching] = useMutation(detachMeasureMutation);
|
||||
|
||||
const onClick = () => {
|
||||
detachMeasure({
|
||||
variables: {
|
||||
input: {
|
||||
riskId: riskId,
|
||||
measureId: measure.id,
|
||||
},
|
||||
connections: [connectionId],
|
||||
},
|
||||
});
|
||||
};
|
||||
return (
|
||||
<Tr>
|
||||
<Td>{measure.name}</Td>
|
||||
<Td>
|
||||
<Button
|
||||
disabled={isFetching}
|
||||
icon={IconTrashCan}
|
||||
className="ml-auto"
|
||||
variant="quaternary"
|
||||
onClick={onClick}
|
||||
>
|
||||
{__("Unlink")}
|
||||
</Button>
|
||||
</Td>
|
||||
</Tr>
|
||||
<LinkedMeasuresCard
|
||||
disabled={isLoading}
|
||||
measures={measures}
|
||||
onAttach={attachMeasure}
|
||||
onDetach={detachMeasure}
|
||||
params={{ riskId: data.id }}
|
||||
connectionId={connectionId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
202
apps/console2/src/pages/organizations/risks/tabs/__generated__/RiskMeasuresTabCreateMutation.graphql.ts
generated
Normal file
202
apps/console2/src/pages/organizations/risks/tabs/__generated__/RiskMeasuresTabCreateMutation.graphql.ts
generated
Normal file
@@ -0,0 +1,202 @@
|
||||
/**
|
||||
* @generated SignedSource<<6e27a069ae5675b9c83e369566a91d01>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
import { ConcreteRequest } from 'relay-runtime';
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type CreateRiskMeasureMappingInput = {
|
||||
measureId: string;
|
||||
riskId: string;
|
||||
};
|
||||
export type RiskMeasuresTabCreateMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: CreateRiskMeasureMappingInput;
|
||||
};
|
||||
export type RiskMeasuresTabCreateMutation$data = {
|
||||
readonly createRiskMeasureMapping: {
|
||||
readonly measureEdge: {
|
||||
readonly node: {
|
||||
readonly id: string;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"LinkedMeasuresCardFragment">;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
export type RiskMeasuresTabCreateMutation = {
|
||||
response: RiskMeasuresTabCreateMutation$data;
|
||||
variables: RiskMeasuresTabCreateMutation$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": "RiskMeasuresTabCreateMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateRiskMeasureMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createRiskMeasureMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "MeasureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "measureEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Measure",
|
||||
"kind": "LinkedField",
|
||||
"name": "node",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
(v3/*: any*/),
|
||||
{
|
||||
"args": null,
|
||||
"kind": "FragmentSpread",
|
||||
"name": "LinkedMeasuresCardFragment"
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"type": "Mutation",
|
||||
"abstractKey": null
|
||||
},
|
||||
"kind": "Request",
|
||||
"operation": {
|
||||
"argumentDefinitions": [
|
||||
(v1/*: any*/),
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "RiskMeasuresTabCreateMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": (v2/*: any*/),
|
||||
"concreteType": "CreateRiskMeasureMappingPayload",
|
||||
"kind": "LinkedField",
|
||||
"name": "createRiskMeasureMapping",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "MeasureEdge",
|
||||
"kind": "LinkedField",
|
||||
"name": "measureEdge",
|
||||
"plural": false,
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"concreteType": "Measure",
|
||||
"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": "state",
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"filters": null,
|
||||
"handle": "prependEdge",
|
||||
"key": "",
|
||||
"kind": "LinkedHandle",
|
||||
"name": "measureEdge",
|
||||
"handleArgs": [
|
||||
{
|
||||
"kind": "Variable",
|
||||
"name": "connections",
|
||||
"variableName": "connections"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"storageKey": null
|
||||
}
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "3b141d4117959b1f6efed67e8aa655cf",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "RiskMeasuresTabCreateMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation RiskMeasuresTabCreateMutation(\n $input: CreateRiskMeasureMappingInput!\n) {\n createRiskMeasureMapping(input: $input) {\n measureEdge {\n node {\n id\n ...LinkedMeasuresCardFragment\n }\n }\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "982d31266eda233518c48f1fd0cd2f46";
|
||||
|
||||
export default node;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<327cdb825c0029c21d8812b782ecf14f>>
|
||||
* @generated SignedSource<<c34aab708f8713173639ae4b7aacdf10>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -13,18 +13,18 @@ export type DeleteRiskMeasureMappingInput = {
|
||||
measureId: string;
|
||||
riskId: string;
|
||||
};
|
||||
export type MeasureLinkDialogDetachMutation$variables = {
|
||||
export type RiskMeasuresTabDetachMutation$variables = {
|
||||
connections: ReadonlyArray<string>;
|
||||
input: DeleteRiskMeasureMappingInput;
|
||||
};
|
||||
export type MeasureLinkDialogDetachMutation$data = {
|
||||
export type RiskMeasuresTabDetachMutation$data = {
|
||||
readonly deleteRiskMeasureMapping: {
|
||||
readonly deletedMeasureId: string;
|
||||
};
|
||||
};
|
||||
export type MeasureLinkDialogDetachMutation = {
|
||||
response: MeasureLinkDialogDetachMutation$data;
|
||||
variables: MeasureLinkDialogDetachMutation$variables;
|
||||
export type RiskMeasuresTabDetachMutation = {
|
||||
response: RiskMeasuresTabDetachMutation$data;
|
||||
variables: RiskMeasuresTabDetachMutation$variables;
|
||||
};
|
||||
|
||||
const node: ConcreteRequest = (function(){
|
||||
@@ -60,7 +60,7 @@ return {
|
||||
],
|
||||
"kind": "Fragment",
|
||||
"metadata": null,
|
||||
"name": "MeasureLinkDialogDetachMutation",
|
||||
"name": "RiskMeasuresTabDetachMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
@@ -85,7 +85,7 @@ return {
|
||||
(v0/*: any*/)
|
||||
],
|
||||
"kind": "Operation",
|
||||
"name": "MeasureLinkDialogDetachMutation",
|
||||
"name": "RiskMeasuresTabDetachMutation",
|
||||
"selections": [
|
||||
{
|
||||
"alias": null,
|
||||
@@ -118,16 +118,16 @@ return {
|
||||
]
|
||||
},
|
||||
"params": {
|
||||
"cacheID": "8a54f580a892f60f24c65ec927c7340c",
|
||||
"cacheID": "81260f49b2f31c4a12f87b38bab7a683",
|
||||
"id": null,
|
||||
"metadata": {},
|
||||
"name": "MeasureLinkDialogDetachMutation",
|
||||
"name": "RiskMeasuresTabDetachMutation",
|
||||
"operationKind": "mutation",
|
||||
"text": "mutation MeasureLinkDialogDetachMutation(\n $input: DeleteRiskMeasureMappingInput!\n) {\n deleteRiskMeasureMapping(input: $input) {\n deletedMeasureId\n }\n}\n"
|
||||
"text": "mutation RiskMeasuresTabDetachMutation(\n $input: DeleteRiskMeasureMappingInput!\n) {\n deleteRiskMeasureMapping(input: $input) {\n deletedMeasureId\n }\n}\n"
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "e02a17e00e2c07881dcae17fef9513eb";
|
||||
(node as any).hash = "c5adc6d900f0da15ec3acf7db304d410";
|
||||
|
||||
export default node;
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @generated SignedSource<<62ef6617862f528f26edaffe3adc3069>>
|
||||
* @generated SignedSource<<5a37b1ce3c0c17ce36fc3e1157eadf8e>>
|
||||
* @lightSyntaxTransform
|
||||
* @nogrep
|
||||
*/
|
||||
@@ -9,7 +9,6 @@
|
||||
// @ts-nocheck
|
||||
|
||||
import { ReaderFragment } from 'relay-runtime';
|
||||
export type MeasureState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED";
|
||||
import { FragmentRefs } from "relay-runtime";
|
||||
export type RiskMeasuresTabFragment$data = {
|
||||
readonly id: string;
|
||||
@@ -17,12 +16,8 @@ export type RiskMeasuresTabFragment$data = {
|
||||
readonly __id: string;
|
||||
readonly edges: ReadonlyArray<{
|
||||
readonly node: {
|
||||
readonly category: string;
|
||||
readonly createdAt: any;
|
||||
readonly description: string;
|
||||
readonly id: string;
|
||||
readonly name: string;
|
||||
readonly state: MeasureState;
|
||||
readonly " $fragmentSpreads": FragmentRefs<"LinkedMeasuresCardFragment">;
|
||||
};
|
||||
}>;
|
||||
};
|
||||
@@ -85,39 +80,9 @@ return {
|
||||
"selections": [
|
||||
(v0/*: 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": "category",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "createdAt",
|
||||
"storageKey": null
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
"args": null,
|
||||
"kind": "ScalarField",
|
||||
"name": "state",
|
||||
"storageKey": null
|
||||
"kind": "FragmentSpread",
|
||||
"name": "LinkedMeasuresCardFragment"
|
||||
},
|
||||
{
|
||||
"alias": null,
|
||||
@@ -185,6 +150,6 @@ return {
|
||||
};
|
||||
})();
|
||||
|
||||
(node as any).hash = "e04c5962a40b48aade0d1e4a31f0c583";
|
||||
(node as any).hash = "e928c83f7fea74daa5663d6f80518419";
|
||||
|
||||
export default node;
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { loadQuery } from "react-relay";
|
||||
import { PageSkeleton } from "/components/skeletons/PageSkeleton";
|
||||
import { relayEnvironment } from "/providers/RelayProviders";
|
||||
import { frameworksQuery } from "/hooks/graph/FrameworkGraph";
|
||||
import {
|
||||
frameworksQuery,
|
||||
frameworkNodeQuery,
|
||||
} from "/hooks/graph/FrameworkGraph";
|
||||
import type { AppRoute } from "/routes";
|
||||
import { lazy } from "react";
|
||||
|
||||
@@ -15,4 +18,13 @@ export const frameworkRoutes = [
|
||||
() => import("/pages/organizations/frameworks/FrameworksPage")
|
||||
),
|
||||
},
|
||||
{
|
||||
path: "frameworks/:frameworkId/:controlId?",
|
||||
fallback: PageSkeleton,
|
||||
queryLoader: ({ frameworkId }) =>
|
||||
loadQuery(relayEnvironment, frameworkNodeQuery, { frameworkId }),
|
||||
Component: lazy(
|
||||
() => import("/pages/organizations/frameworks/FrameworkDetailPage")
|
||||
),
|
||||
},
|
||||
] satisfies AppRoute[];
|
||||
|
||||
Reference in New Issue
Block a user