From 70b440eaa1936bee9a224bb442d54db2a299578d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Fri, 4 Apr 2025 13:36:27 +0400 Subject: [PATCH] Fix framework page displaying first control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Émile Ré --- .../organizations/OrganizationBreadcrumb.tsx | 1 + .../src/pages/organizations/Routes.tsx | 5 +- .../frameworks/FrameworkLayoutView.tsx | 29 +- .../frameworks/FrameworkPage.tsx | 38 + .../frameworks/FrameworkView.tsx | 70 ++ ...meworkLayoutViewDeleteMutation.graphql.ts} | 24 +- .../FrameworkLayoutViewQuery.graphql.ts | 316 +++++++ .../FrameworkViewQuery.graphql.ts | 225 +++-- .../frameworks/controls/Control.tsx | 779 +++++++++++++++++ .../frameworks/controls/ControlView.tsx | 783 +----------------- ...reateMitigationMappingMutation.graphql.ts} | 24 +- ...eleteMitigationMappingMutation.graphql.ts} | 24 +- .../ControlFragment_Control.graphql.ts | 66 ++ ... ControlLinkedMitigationsQuery.graphql.ts} | 24 +- ...olOrganizationMitigationsQuery.graphql.ts} | 24 +- .../__generated__/ControlViewQuery.graphql.ts | 76 +- 16 files changed, 1505 insertions(+), 1003 deletions(-) create mode 100644 apps/console/src/pages/organizations/frameworks/FrameworkPage.tsx create mode 100644 apps/console/src/pages/organizations/frameworks/FrameworkView.tsx rename apps/console/src/pages/organizations/frameworks/__generated__/{FrameworkViewDeleteMutation.graphql.ts => FrameworkLayoutViewDeleteMutation.graphql.ts} (75%) create mode 100644 apps/console/src/pages/organizations/frameworks/__generated__/FrameworkLayoutViewQuery.graphql.ts create mode 100644 apps/console/src/pages/organizations/frameworks/controls/Control.tsx rename apps/console/src/pages/organizations/frameworks/controls/__generated__/{ControlViewCreateMitigationMappingMutation.graphql.ts => ControlCreateMitigationMappingMutation.graphql.ts} (63%) rename apps/console/src/pages/organizations/frameworks/controls/__generated__/{ControlViewDeleteMitigationMappingMutation.graphql.ts => ControlDeleteMitigationMappingMutation.graphql.ts} (63%) create mode 100644 apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlFragment_Control.graphql.ts rename apps/console/src/pages/organizations/frameworks/controls/__generated__/{ControlViewLinkedMitigationsQuery.graphql.ts => ControlLinkedMitigationsQuery.graphql.ts} (84%) rename apps/console/src/pages/organizations/frameworks/controls/__generated__/{ControlViewOrganizationMitigationsQuery.graphql.ts => ControlOrganizationMitigationsQuery.graphql.ts} (83%) diff --git a/apps/console/src/pages/organizations/OrganizationBreadcrumb.tsx b/apps/console/src/pages/organizations/OrganizationBreadcrumb.tsx index 372773da9..116c92ed5 100644 --- a/apps/console/src/pages/organizations/OrganizationBreadcrumb.tsx +++ b/apps/console/src/pages/organizations/OrganizationBreadcrumb.tsx @@ -388,6 +388,7 @@ function BreadcrumbControl() { { controlId: controlId! }, { fetchPolicy: "store-or-network" } ); + return ( <> diff --git a/apps/console/src/pages/organizations/Routes.tsx b/apps/console/src/pages/organizations/Routes.tsx index da3ade72e..02ed1a275 100644 --- a/apps/console/src/pages/organizations/Routes.tsx +++ b/apps/console/src/pages/organizations/Routes.tsx @@ -6,8 +6,9 @@ import NoOrganizationLayout from "./NoOrganizationLayout"; import OrganizationLayout from "./OrganizationLayout"; import { SettingsPage } from "./SettingsPage"; import { CreateFrameworkPage } from "./frameworks/CreateFrameworkPage"; -import { FrameworkListPage } from "./frameworks/FrameworkListPage"; import { FrameworkLayout } from "./frameworks/FrameworkLayout"; +import { FrameworkListPage } from "./frameworks/FrameworkListPage"; +import { FrameworkPage } from "./frameworks/FrameworkPage"; import { UpdateFrameworkPage } from "./frameworks/UpdateFrameworkPage"; import { ControlPage } from "./frameworks/controls/ControlPage"; import { EditMitigationPage } from "./mitigations/EditMitigationPage"; @@ -40,7 +41,7 @@ export function OrganizationsRoutes() { } /> }> - + } /> } /> } /> diff --git a/apps/console/src/pages/organizations/frameworks/FrameworkLayoutView.tsx b/apps/console/src/pages/organizations/frameworks/FrameworkLayoutView.tsx index 44263cfb8..0beeec505 100644 --- a/apps/console/src/pages/organizations/frameworks/FrameworkLayoutView.tsx +++ b/apps/console/src/pages/organizations/frameworks/FrameworkLayoutView.tsx @@ -18,14 +18,14 @@ import { DialogTitle, } from "@/components/ui/dialog"; import { useToast } from "@/hooks/use-toast"; -import type { FrameworkViewQuery as FrameworkViewQueryType } from "./__generated__/FrameworkViewQuery.graphql"; -import type { FrameworkViewDeleteMutation } from "./__generated__/FrameworkViewDeleteMutation.graphql"; +import type { FrameworkLayoutViewQuery as FrameworkLayoutViewQueryType } from "./__generated__/FrameworkLayoutViewQuery.graphql"; +import type { FrameworkLayoutViewDeleteMutation } from "./__generated__/FrameworkLayoutViewDeleteMutation.graphql"; import { PageTemplate } from "@/components/PageTemplate"; import { FrameworkLayoutViewSkeleton } from "./FrameworkLayout"; import { ControlList } from "./FrameworkLayoutView/ControlList"; -const FrameworkViewQuery = graphql` - query FrameworkViewQuery($frameworkId: ID!) { +const FrameworkLayoutViewQuery = graphql` + query FrameworkLayoutViewQuery($frameworkId: ID!) { node(id: $frameworkId) { id ... on Framework { @@ -35,7 +35,7 @@ const FrameworkViewQuery = graphql` firstControl: controls( first: 1 orderBy: { field: CREATED_AT, direction: ASC } - ) @connection(key: "FrameworkView_firstControl") { + ) @connection(key: "FrameworkLayoutView_firstControl") { edges { node { id @@ -50,7 +50,7 @@ const FrameworkViewQuery = graphql` `; const DeleteFrameworkMutation = graphql` - mutation FrameworkViewDeleteMutation( + mutation FrameworkLayoutViewDeleteMutation( $input: DeleteFrameworkInput! $connections: [ID!]! ) { @@ -60,12 +60,12 @@ const DeleteFrameworkMutation = graphql` } `; -function FrameworkViewContent({ +function FrameworkLayoutViewContent({ queryRef, }: { - queryRef: PreloadedQuery; + queryRef: PreloadedQuery; }) { - const data = usePreloadedQuery(FrameworkViewQuery, queryRef); + const data = usePreloadedQuery(FrameworkLayoutViewQuery, queryRef); const framework = data.node; const navigate = useNavigate(); const { organizationId } = useParams(); @@ -74,7 +74,7 @@ function FrameworkViewContent({ const [isDeleting, setIsDeleting] = useState(false); // Setup delete mutation - const [commitDeleteMutation] = useMutation( + const [commitDeleteMutation] = useMutation( DeleteFrameworkMutation ); @@ -186,10 +186,11 @@ function FrameworkViewContent({ ); } -export default function FrameworkView() { +export default function FrameworkLayoutView() { const { frameworkId } = useParams(); - const [queryRef, loadQuery] = - useQueryLoader(FrameworkViewQuery); + const [queryRef, loadQuery] = useQueryLoader( + FrameworkLayoutViewQuery + ); useEffect(() => { loadQuery({ frameworkId: frameworkId! }); @@ -201,7 +202,7 @@ export default function FrameworkView() { return ( }> - + ); } diff --git a/apps/console/src/pages/organizations/frameworks/FrameworkPage.tsx b/apps/console/src/pages/organizations/frameworks/FrameworkPage.tsx new file mode 100644 index 000000000..a25402044 --- /dev/null +++ b/apps/console/src/pages/organizations/frameworks/FrameworkPage.tsx @@ -0,0 +1,38 @@ +import { Card, CardContent } from "@/components/ui/card"; +import { Suspense } from "react"; +import { useLocation } from "react-router"; +import { lazy } from "@probo/react-lazy"; +import ErrorBoundary from "@/components/ErrorBoundary"; + +const FrameworkView = lazy(() => import("./FrameworkView")); + +export function FrameworkViewSkeleton() { + return ( +
+ {[1, 2, 3].map((i) => ( + + +
+
+
+
+
+
+ + + ))} +
+ ); +} + +export function FrameworkPage() { + const location = useLocation(); + + return ( + }> + + + + + ); +} diff --git a/apps/console/src/pages/organizations/frameworks/FrameworkView.tsx b/apps/console/src/pages/organizations/frameworks/FrameworkView.tsx new file mode 100644 index 000000000..bb886f886 --- /dev/null +++ b/apps/console/src/pages/organizations/frameworks/FrameworkView.tsx @@ -0,0 +1,70 @@ +import { + graphql, + PreloadedQuery, + usePreloadedQuery, + useQueryLoader, +} from "react-relay"; +import { useParams } from "react-router"; +import { FrameworkViewQuery } from "./__generated__/FrameworkViewQuery.graphql"; +import { Suspense, useEffect } from "react"; +import { Control } from "./controls/Control"; +import { FrameworkViewSkeleton } from "./FrameworkPage"; + +const frameworkViewQuery = graphql` + query FrameworkViewQuery($frameworkId: ID!) { + node(id: $frameworkId) { + id + ... on Framework { + name + description + firstControl: controls( + first: 1 + orderBy: { field: CREATED_AT, direction: ASC } + ) @connection(key: "FrameworkView_firstControl") { + edges { + node { + ...ControlFragment_Control + } + } + } + } + } + } +`; + +function FrameworkViewContent({ + queryRef, +}: { + queryRef: PreloadedQuery; +}) { + const data = usePreloadedQuery( + frameworkViewQuery, + queryRef + ); + + if (!data.node.firstControl) { + return null; + } + + return ; +} + +export default function FrameworkView() { + const { frameworkId } = useParams(); + const [queryRef, loadQuery] = + useQueryLoader(frameworkViewQuery); + + useEffect(() => { + loadQuery({ frameworkId: frameworkId! }); + }, [loadQuery, frameworkId]); + + if (!queryRef) { + return ; + } + + return ( + }> + + + ); +} diff --git a/apps/console/src/pages/organizations/frameworks/__generated__/FrameworkViewDeleteMutation.graphql.ts b/apps/console/src/pages/organizations/frameworks/__generated__/FrameworkLayoutViewDeleteMutation.graphql.ts similarity index 75% rename from apps/console/src/pages/organizations/frameworks/__generated__/FrameworkViewDeleteMutation.graphql.ts rename to apps/console/src/pages/organizations/frameworks/__generated__/FrameworkLayoutViewDeleteMutation.graphql.ts index c3435e149..6990e79f0 100644 --- a/apps/console/src/pages/organizations/frameworks/__generated__/FrameworkViewDeleteMutation.graphql.ts +++ b/apps/console/src/pages/organizations/frameworks/__generated__/FrameworkLayoutViewDeleteMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<9f35422e7d027485b106e6fb8879d460>> + * @generated SignedSource<<615df626961da33fb1e48249870eefe1>> * @lightSyntaxTransform * @nogrep */ @@ -12,18 +12,18 @@ import { ConcreteRequest } from 'relay-runtime'; export type DeleteFrameworkInput = { frameworkId: string; }; -export type FrameworkViewDeleteMutation$variables = { +export type FrameworkLayoutViewDeleteMutation$variables = { connections: ReadonlyArray; input: DeleteFrameworkInput; }; -export type FrameworkViewDeleteMutation$data = { +export type FrameworkLayoutViewDeleteMutation$data = { readonly deleteFramework: { readonly deletedFrameworkId: string; }; }; -export type FrameworkViewDeleteMutation = { - response: FrameworkViewDeleteMutation$data; - variables: FrameworkViewDeleteMutation$variables; +export type FrameworkLayoutViewDeleteMutation = { + response: FrameworkLayoutViewDeleteMutation$data; + variables: FrameworkLayoutViewDeleteMutation$variables; }; const node: ConcreteRequest = (function(){ @@ -59,7 +59,7 @@ return { ], "kind": "Fragment", "metadata": null, - "name": "FrameworkViewDeleteMutation", + "name": "FrameworkLayoutViewDeleteMutation", "selections": [ { "alias": null, @@ -84,7 +84,7 @@ return { (v0/*: any*/) ], "kind": "Operation", - "name": "FrameworkViewDeleteMutation", + "name": "FrameworkLayoutViewDeleteMutation", "selections": [ { "alias": null, @@ -117,16 +117,16 @@ return { ] }, "params": { - "cacheID": "b10bac4b295c5d2a9a564cbba4569633", + "cacheID": "bf9a90fe1038778a5bc29ea20e8b07b9", "id": null, "metadata": {}, - "name": "FrameworkViewDeleteMutation", + "name": "FrameworkLayoutViewDeleteMutation", "operationKind": "mutation", - "text": "mutation FrameworkViewDeleteMutation(\n $input: DeleteFrameworkInput!\n) {\n deleteFramework(input: $input) {\n deletedFrameworkId\n }\n}\n" + "text": "mutation FrameworkLayoutViewDeleteMutation(\n $input: DeleteFrameworkInput!\n) {\n deleteFramework(input: $input) {\n deletedFrameworkId\n }\n}\n" } }; })(); -(node as any).hash = "8b20886821651ffb7e335df58fb46fc9"; +(node as any).hash = "52564100dcf31f83cabd0683a616982c"; export default node; diff --git a/apps/console/src/pages/organizations/frameworks/__generated__/FrameworkLayoutViewQuery.graphql.ts b/apps/console/src/pages/organizations/frameworks/__generated__/FrameworkLayoutViewQuery.graphql.ts new file mode 100644 index 000000000..05a2c4273 --- /dev/null +++ b/apps/console/src/pages/organizations/frameworks/__generated__/FrameworkLayoutViewQuery.graphql.ts @@ -0,0 +1,316 @@ +/** + * @generated SignedSource<<4f0c7f7d3fb0272b838162d615ba905b>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type FrameworkLayoutViewQuery$variables = { + frameworkId: string; +}; +export type FrameworkLayoutViewQuery$data = { + readonly node: { + readonly description?: string; + readonly firstControl?: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly id: string; + readonly name: string; + readonly referenceId: string; + }; + }>; + }; + readonly id: string; + readonly name?: string; + readonly " $fragmentSpreads": FragmentRefs<"ControlList_List">; + }; +}; +export type FrameworkLayoutViewQuery = { + response: FrameworkLayoutViewQuery$data; + variables: FrameworkLayoutViewQuery$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": "description", + "storageKey": null +}, +v5 = { + "kind": "Literal", + "name": "orderBy", + "value": { + "direction": "ASC", + "field": "CREATED_AT" + } +}, +v6 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v7 = [ + { + "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*/), + (v6/*: 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 + } +], +v8 = [ + { + "kind": "Literal", + "name": "first", + "value": 100 + }, + (v5/*: any*/) +], +v9 = [ + "orderBy" +], +v10 = [ + { + "kind": "Literal", + "name": "first", + "value": 1 + }, + (v5/*: any*/) +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "FrameworkLayoutViewQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + (v3/*: any*/), + (v4/*: any*/), + { + "args": null, + "kind": "FragmentSpread", + "name": "ControlList_List" + }, + { + "alias": "firstControl", + "args": [ + (v5/*: any*/) + ], + "concreteType": "ControlConnection", + "kind": "LinkedField", + "name": "__FrameworkLayoutView_firstControl_connection", + "plural": false, + "selections": (v7/*: any*/), + "storageKey": "__FrameworkLayoutView_firstControl_connection(orderBy:{\"direction\":\"ASC\",\"field\":\"CREATED_AT\"})" + } + ], + "type": "Framework", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "FrameworkLayoutViewQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v6/*: any*/), + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + (v3/*: any*/), + (v4/*: any*/), + { + "alias": null, + "args": (v8/*: any*/), + "concreteType": "ControlConnection", + "kind": "LinkedField", + "name": "controls", + "plural": false, + "selections": (v7/*: any*/), + "storageKey": "controls(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"CREATED_AT\"})" + }, + { + "alias": null, + "args": (v8/*: any*/), + "filters": (v9/*: any*/), + "handle": "connection", + "key": "FrameworkView_controls", + "kind": "LinkedHandle", + "name": "controls" + }, + { + "alias": "firstControl", + "args": (v10/*: any*/), + "concreteType": "ControlConnection", + "kind": "LinkedField", + "name": "controls", + "plural": false, + "selections": (v7/*: any*/), + "storageKey": "controls(first:1,orderBy:{\"direction\":\"ASC\",\"field\":\"CREATED_AT\"})" + }, + { + "alias": "firstControl", + "args": (v10/*: any*/), + "filters": (v9/*: any*/), + "handle": "connection", + "key": "FrameworkLayoutView_firstControl", + "kind": "LinkedHandle", + "name": "controls" + } + ], + "type": "Framework", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "98ca0e0c503bb7c5e3e538e8831eeefa", + "id": null, + "metadata": { + "connection": [ + { + "count": null, + "cursor": null, + "direction": "forward", + "path": [ + "node", + "firstControl" + ] + } + ] + }, + "name": "FrameworkLayoutViewQuery", + "operationKind": "query", + "text": "query FrameworkLayoutViewQuery(\n $frameworkId: ID!\n) {\n node(id: $frameworkId) {\n __typename\n id\n ... on Framework {\n name\n description\n ...ControlList_List\n firstControl: controls(first: 1, orderBy: {field: CREATED_AT, direction: ASC}) {\n edges {\n node {\n id\n referenceId\n name\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n\nfragment ControlList_List on Framework {\n controls(first: 100, orderBy: {field: CREATED_AT, direction: ASC}) {\n edges {\n node {\n id\n referenceId\n name\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "1935177e374d21015e102745c4d51e94"; + +export default node; diff --git a/apps/console/src/pages/organizations/frameworks/__generated__/FrameworkViewQuery.graphql.ts b/apps/console/src/pages/organizations/frameworks/__generated__/FrameworkViewQuery.graphql.ts index b3943f294..fcfa7a588 100644 --- a/apps/console/src/pages/organizations/frameworks/__generated__/FrameworkViewQuery.graphql.ts +++ b/apps/console/src/pages/organizations/frameworks/__generated__/FrameworkViewQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<0476147756ad426abb8651c6a62821f3>> + * @generated SignedSource<<2f29bc26fdb324cad6eb8d7bdd48447f>> * @lightSyntaxTransform * @nogrep */ @@ -19,15 +19,12 @@ export type FrameworkViewQuery$data = { readonly firstControl?: { readonly edges: ReadonlyArray<{ readonly node: { - readonly id: string; - readonly name: string; - readonly referenceId: string; + readonly " $fragmentSpreads": FragmentRefs<"ControlFragment_Control">; }; }>; }; readonly id: string; readonly name?: string; - readonly " $fragmentSpreads": FragmentRefs<"ControlList_List">; }; }; export type FrameworkViewQuery = { @@ -86,84 +83,39 @@ v6 = { "name": "__typename", "storageKey": null }, -v7 = [ - { - "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*/), - (v6/*: 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 - } -], -v8 = [ - { - "kind": "Literal", - "name": "first", - "value": 100 - }, - (v5/*: any*/) -], +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 = [ - "orderBy" -], -v10 = [ { "kind": "Literal", "name": "first", @@ -192,11 +144,6 @@ return { "selections": [ (v3/*: any*/), (v4/*: any*/), - { - "args": null, - "kind": "FragmentSpread", - "name": "ControlList_List" - }, { "alias": "firstControl", "args": [ @@ -206,7 +153,38 @@ return { "kind": "LinkedField", "name": "__FrameworkView_firstControl_connection", "plural": false, - "selections": (v7/*: any*/), + "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": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "ControlFragment_Control" + }, + (v6/*: any*/) + ], + "storageKey": null + }, + (v7/*: any*/) + ], + "storageKey": null + }, + (v8/*: any*/) + ], "storageKey": "__FrameworkView_firstControl_connection(orderBy:{\"direction\":\"ASC\",\"field\":\"CREATED_AT\"})" } ], @@ -241,39 +219,58 @@ return { "selections": [ (v3/*: any*/), (v4/*: any*/), - { - "alias": null, - "args": (v8/*: any*/), - "concreteType": "ControlConnection", - "kind": "LinkedField", - "name": "controls", - "plural": false, - "selections": (v7/*: any*/), - "storageKey": "controls(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"CREATED_AT\"})" - }, - { - "alias": null, - "args": (v8/*: any*/), - "filters": (v9/*: any*/), - "handle": "connection", - "key": "FrameworkView_controls", - "kind": "LinkedHandle", - "name": "controls" - }, { "alias": "firstControl", - "args": (v10/*: any*/), + "args": (v9/*: any*/), "concreteType": "ControlConnection", "kind": "LinkedField", "name": "controls", "plural": false, - "selections": (v7/*: any*/), + "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*/), + (v4/*: any*/), + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "referenceId", + "storageKey": null + }, + (v6/*: any*/) + ], + "storageKey": null + }, + (v7/*: any*/) + ], + "storageKey": null + }, + (v8/*: any*/) + ], "storageKey": "controls(first:1,orderBy:{\"direction\":\"ASC\",\"field\":\"CREATED_AT\"})" }, { "alias": "firstControl", - "args": (v10/*: any*/), - "filters": (v9/*: any*/), + "args": (v9/*: any*/), + "filters": [ + "orderBy" + ], "handle": "connection", "key": "FrameworkView_firstControl", "kind": "LinkedHandle", @@ -289,7 +286,7 @@ return { ] }, "params": { - "cacheID": "17fd996ebe2e664652323e290a027cfa", + "cacheID": "c7d856630ed8706055e1ff572d0e7293", "id": null, "metadata": { "connection": [ @@ -306,11 +303,11 @@ return { }, "name": "FrameworkViewQuery", "operationKind": "query", - "text": "query FrameworkViewQuery(\n $frameworkId: ID!\n) {\n node(id: $frameworkId) {\n __typename\n id\n ... on Framework {\n name\n description\n ...ControlList_List\n firstControl: controls(first: 1, orderBy: {field: CREATED_AT, direction: ASC}) {\n edges {\n node {\n id\n referenceId\n name\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n\nfragment ControlList_List on Framework {\n controls(first: 100, orderBy: {field: CREATED_AT, direction: ASC}) {\n edges {\n node {\n id\n referenceId\n name\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n" + "text": "query FrameworkViewQuery(\n $frameworkId: ID!\n) {\n node(id: $frameworkId) {\n __typename\n id\n ... on Framework {\n name\n description\n firstControl: controls(first: 1, orderBy: {field: CREATED_AT, direction: ASC}) {\n edges {\n node {\n ...ControlFragment_Control\n id\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n\nfragment ControlFragment_Control on Control {\n id\n description\n name\n referenceId\n}\n" } }; })(); -(node as any).hash = "2567d3e2996074a21656830be7f807f1"; +(node as any).hash = "44f29d19bae9e1a0426f44f5d0edca45"; export default node; diff --git a/apps/console/src/pages/organizations/frameworks/controls/Control.tsx b/apps/console/src/pages/organizations/frameworks/controls/Control.tsx new file mode 100644 index 000000000..c3887bc19 --- /dev/null +++ b/apps/console/src/pages/organizations/frameworks/controls/Control.tsx @@ -0,0 +1,779 @@ +import { Button } from "@/components/ui/button"; +import { DialogHeader, DialogFooter } from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; +import { useToast } from "@/hooks/use-toast"; +import { + Dialog, + DialogContent, + DialogTitle, + DialogDescription, +} from "@radix-ui/react-dialog"; +import { + Select, + SelectTrigger, + SelectValue, + SelectContent, + SelectItem, +} from "@radix-ui/react-select"; +import { Search, Loader2, X, LinkIcon } from "lucide-react"; +import { useState, useEffect, useCallback } from "react"; +import { + fetchQuery, + graphql, + useFragment, + useMutation, + useRelayEnvironment, +} from "react-relay"; +import { useParams, Link } from "react-router"; +import { + ControlLinkedMitigationsQuery$data, + ControlLinkedMitigationsQuery, +} from "./__generated__/ControlLinkedMitigationsQuery.graphql"; +import { + ControlOrganizationMitigationsQuery$data, + ControlOrganizationMitigationsQuery, +} from "./__generated__/ControlOrganizationMitigationsQuery.graphql"; +import { ControlFragment_Control$key } from "./__generated__/ControlFragment_Control.graphql"; + +const controlFragment = graphql` + fragment ControlFragment_Control on Control { + id + description + name + referenceId + } +`; + +// New query to fetch linked mitigations +const linkedMitigationsQuery = graphql` + query ControlLinkedMitigationsQuery($controlId: ID!) { + control: node(id: $controlId) { + id + ... on Control { + mitigations(first: 100) @connection(key: "Control__mitigations") { + edges { + node { + id + name + description + category + importance + state + } + } + } + } + } + } +`; + +// Query to fetch all mitigations for the organization +const organizationMitigationsQuery = graphql` + query ControlOrganizationMitigationsQuery($organizationId: ID!) { + organization: node(id: $organizationId) { + id + ... on Organization { + mitigations(first: 100) @connection(key: "Organization__mitigations") { + edges { + node { + id + name + description + category + importance + state + } + } + } + } + } + } +`; + +// Mutation to create a mapping between a control and a mitigation +const createMitigationMappingMutation = graphql` + mutation ControlCreateMitigationMappingMutation( + $input: CreateControlMappingInput! + ) { + createControlMapping(input: $input) { + success + } + } +`; + +// Mutation to delete a mapping between a control and a mitigation +const deleteMitigationMappingMutation = graphql` + mutation ControlDeleteMitigationMappingMutation( + $input: DeleteControlMappingInput! + ) { + deleteControlMapping(input: $input) { + success + } + } +`; + +export function Control({ + controlKey, +}: { + controlKey: ControlFragment_Control$key; +}) { + const { organizationId /* frameworkId */ } = useParams<{ + organizationId: string; + frameworkId: string; + }>(); + const control = useFragment(controlFragment, controlKey); + const { toast } = useToast(); + const environment = useRelayEnvironment(); + + // State for mitigation mapping + const [isMitigationMappingDialogOpen, setIsMitigationMappingDialogOpen] = + useState(false); + const [linkedMitigationsData, setLinkedMitigationsData] = + useState(null); + const [organizationMitigationsData, setOrganizationMitigationsData] = + useState(null); + const [mitigationSearchQuery, setMitigationSearchQuery] = useState(""); + const [isLoadingMitigations, setIsLoadingMitigations] = useState(false); + const [isLinkingMitigation, setIsLinkingMitigation] = useState(false); + const [isUnlinkingMitigation, setIsUnlinkingMitigation] = useState(false); + const [categoryFilter, setCategoryFilter] = useState(null); + + // Create mutation hooks + const [commitCreateMitigationMapping] = useMutation( + createMitigationMappingMutation + ); + const [commitDeleteMitigationMapping] = useMutation( + deleteMitigationMappingMutation + ); + + // Load initial linked mitigations data + useEffect(() => { + if (control.id) { + setIsLoadingMitigations(true); + fetchQuery( + environment, + linkedMitigationsQuery, + { + controlId: control.id, + } + ).subscribe({ + next: (data) => { + setLinkedMitigationsData(data); + setIsLoadingMitigations(false); + }, + error: (error: Error) => { + console.error("Error loading initial mitigations:", error); + setIsLoadingMitigations(false); + }, + }); + } + }, [control.id, environment]); + + // Load mitigations data + const loadMitigationsData = useCallback(() => { + if (!organizationId || !control.id) return; + + setIsLoadingMitigations(true); + + // Fetch all mitigations for the organization + fetchQuery( + environment, + organizationMitigationsQuery, + { + organizationId, + } + ).subscribe({ + next: (data) => { + setOrganizationMitigationsData(data); + }, + complete: () => { + // Fetch linked mitigations for this control + fetchQuery( + environment, + linkedMitigationsQuery, + { + controlId: control.id, + } + ).subscribe({ + next: (data) => { + setLinkedMitigationsData(data); + setIsLoadingMitigations(false); + }, + error: (error: Error) => { + console.error("Error fetching linked mitigations:", error); + setIsLoadingMitigations(false); + toast({ + title: "Error", + description: "Failed to load linked mitigations.", + variant: "destructive", + }); + }, + }); + }, + error: (error: Error) => { + console.error("Error fetching organization mitigations:", error); + setIsLoadingMitigations(false); + toast({ + title: "Error", + description: "Failed to load mitigations.", + variant: "destructive", + }); + }, + }); + }, [control.id, environment, organizationId, toast]); + + // Helper functions + const getMitigations = useCallback(() => { + if (!organizationMitigationsData?.organization?.mitigations?.edges) + return []; + return organizationMitigationsData.organization.mitigations.edges.map( + (edge) => edge.node + ); + }, [organizationMitigationsData]); + + const getLinkedMitigations = useCallback(() => { + if (!linkedMitigationsData?.control?.mitigations?.edges) return []; + return linkedMitigationsData.control.mitigations.edges.map( + (edge) => edge.node + ); + }, [linkedMitigationsData]); + + const isMitigationLinked = useCallback( + (mitigationId: string) => { + const linkedMitigations = getLinkedMitigations(); + return linkedMitigations.some( + (mitigation) => mitigation.id === mitigationId + ); + }, + [getLinkedMitigations] + ); + + const getMitigationCategories = useCallback(() => { + const mitigations = getMitigations(); + const categories = new Set(); + + mitigations.forEach((mitigation) => { + if (mitigation.category) { + categories.add(mitigation.category); + } + }); + + return Array.from(categories).sort(); + }, [getMitigations]); + + const filteredMitigations = useCallback(() => { + const mitigations = getMitigations(); + if (!mitigationSearchQuery && !categoryFilter) return mitigations; + + return mitigations.filter((mitigation) => { + // Filter by search query + const matchesSearch = + !mitigationSearchQuery || + mitigation.name + .toLowerCase() + .includes(mitigationSearchQuery.toLowerCase()) || + (mitigation.description && + mitigation.description + .toLowerCase() + .includes(mitigationSearchQuery.toLowerCase())); + + // Filter by category + const matchesCategory = + !categoryFilter || + categoryFilter === "all" || + mitigation.category === categoryFilter; + + return matchesSearch && matchesCategory; + }); + }, [categoryFilter, getMitigations, mitigationSearchQuery]); + + // Handle link/unlink functions + const handleLinkMitigation = useCallback( + (mitigationId: string) => { + if (!control.id) return; + + setIsLinkingMitigation(true); + + commitCreateMitigationMapping({ + variables: { + input: { + controlId: control.id, + mitigationId: mitigationId, + }, + }, + onCompleted: (_, errors) => { + setIsLinkingMitigation(false); + + if (errors) { + console.error("Error linking mitigation:", errors); + toast({ + title: "Error", + description: "Failed to link mitigation. Please try again.", + variant: "destructive", + }); + return; + } + + // Refresh linked mitigations data + fetchQuery( + environment, + linkedMitigationsQuery, + { + controlId: control.id, + } + ).subscribe({ + next: (data) => { + setLinkedMitigationsData(data); + }, + error: (error: Error) => { + console.error("Error refreshing linked mitigations:", error); + }, + }); + + toast({ + title: "Success", + description: "Mitigation successfully linked to control.", + }); + }, + onError: (error) => { + setIsLinkingMitigation(false); + console.error("Error linking mitigation:", error); + toast({ + title: "Error", + description: "Failed to link mitigation. Please try again.", + variant: "destructive", + }); + }, + }); + }, + [commitCreateMitigationMapping, control.id, environment, toast] + ); + + const handleUnlinkMitigation = useCallback( + (mitigationId: string) => { + if (!control.id) return; + + setIsUnlinkingMitigation(true); + + commitDeleteMitigationMapping({ + variables: { + input: { + controlId: control.id, + mitigationId: mitigationId, + }, + }, + onCompleted: (_, errors) => { + setIsUnlinkingMitigation(false); + + if (errors) { + console.error("Error unlinking mitigation:", errors); + toast({ + title: "Error", + description: "Failed to unlink mitigation. Please try again.", + variant: "destructive", + }); + return; + } + + // Refresh linked mitigations data + fetchQuery( + environment, + linkedMitigationsQuery, + { + controlId: control.id, + } + ).subscribe({ + next: (data) => { + setLinkedMitigationsData(data); + }, + error: (error: Error) => { + console.error("Error refreshing linked mitigations:", error); + }, + }); + + toast({ + title: "Success", + description: "Mitigation successfully unlinked from control.", + }); + }, + onError: (error) => { + setIsUnlinkingMitigation(false); + console.error("Error unlinking mitigation:", error); + toast({ + title: "Error", + description: "Failed to unlink mitigation. Please try again.", + variant: "destructive", + }); + }, + }); + }, + [commitDeleteMitigationMapping, control.id, environment, toast] + ); + + const handleOpenMitigationMappingDialog = useCallback(() => { + loadMitigationsData(); + setIsMitigationMappingDialogOpen(true); + }, [loadMitigationsData]); + + // UI helper functions + const formatImportance = (importance: string | undefined): string => { + if (!importance) return "Unknown"; + + switch (importance) { + case "LOW": + return "Low"; + case "MEDIUM": + return "Medium"; + case "HIGH": + return "High"; + case "CRITICAL": + return "Critical"; + default: + return importance; + } + }; + + const formatState = (state: string | undefined): string => { + if (!state) return "Unknown"; + + switch (state) { + case "NOT_STARTED": + return "Not Started"; + case "IN_PROGRESS": + return "In Progress"; + case "IMPLEMENTED": + return "Implemented"; + case "NOT_APPLICABLE": + return "Not Applicable"; + default: + return state; + } + }; + + const getImportanceColor = (importance: string | undefined): string => { + if (!importance) return "bg-gray-100 text-gray-800"; + + switch (importance) { + case "LOW": + return "bg-blue-100 text-blue-800"; + case "MEDIUM": + return "bg-yellow-100 text-yellow-800"; + case "HIGH": + return "bg-orange-100 text-orange-800"; + case "CRITICAL": + return "bg-red-100 text-red-800"; + default: + return "bg-gray-100 text-gray-800"; + } + }; + + const getStateColor = (state: string | undefined): string => { + if (!state) return "bg-gray-100 text-gray-800"; + + switch (state) { + case "NOT_STARTED": + return "bg-gray-100 text-gray-800"; + case "IN_PROGRESS": + return "bg-blue-100 text-blue-800"; + case "IMPLEMENTED": + return "bg-green-100 text-green-800"; + case "NOT_APPLICABLE": + return "bg-purple-100 text-purple-800"; + default: + return "bg-gray-100 text-gray-800"; + } + }; + + return ( +
+
+ {control.referenceId} +
+
+

{control.name}

+ + {/* Control Description */} + {control.description && ( +
{control.description}
+ )} + + {/* Security Measures Section */} +
+ {/* Mitigation Mapping Dialog */} + + + + Link Security Measures to Control + + Search and select security measures to link to this control. + This helps track which security measures address this control. + + + +
+
+
+ + setMitigationSearchQuery(e.target.value)} + className="w-full pl-10" + /> +
+
+
+ +
+
+ +
+ {isLoadingMitigations ? ( +
+ + Loading security measures... +
+ ) : ( +
+ {filteredMitigations().length === 0 ? ( +
+ No security measures found. Try adjusting your search or + select a different category. +
+ ) : ( + + + + + + + + + + + {filteredMitigations().map((mitigation) => { + const isLinked = isMitigationLinked(mitigation.id); + return ( + + + + + + + ); + })} + +
Name + Importance + State + Actions +
+
+ {mitigation.name} +
+ {mitigation.description && ( +
+ {mitigation.description} +
+ )} +
+
+ {formatImportance(mitigation.importance)} +
+
+
+ {formatState(mitigation.state)} +
+
+ {isLinked ? ( + + ) : ( + + )} +
+ )} +
+ )} +
+ + + + +
+
+ + {/* Linked Mitigations List */} +
+
+

+ Security measures +

+ +
+ + {isLoadingMitigations ? ( +
+ + Loading security measures... +
+ ) : linkedMitigationsData?.control?.mitigations?.edges && + linkedMitigationsData.control.mitigations.edges.length > 0 ? ( +
+ + + + + + + + + + + {getLinkedMitigations().map((mitigation) => ( + + + + + + + ))} + +
NameImportanceState + Actions +
+
{mitigation.name}
+ {mitigation.description && ( +
+ {mitigation.description} +
+ )} +
+
+ {formatImportance(mitigation.importance)} +
+
+
+ {formatState(mitigation.state)} +
+
+
+ + +
+
+
+ ) : ( +
+ No security measures linked to this control yet. Click + "Link Security Measures" to connect some. +
+ )} +
+
+
+
+ ); +} diff --git a/apps/console/src/pages/organizations/frameworks/controls/ControlView.tsx b/apps/console/src/pages/organizations/frameworks/controls/ControlView.tsx index 6682fba31..03dbbc3df 100644 --- a/apps/console/src/pages/organizations/frameworks/controls/ControlView.tsx +++ b/apps/console/src/pages/organizations/frameworks/controls/ControlView.tsx @@ -3,802 +3,33 @@ import { PreloadedQuery, usePreloadedQuery, useQueryLoader, - useMutation, - fetchQuery, - useRelayEnvironment, } from "react-relay"; import { ControlViewSkeleton } from "./ControlPage"; -import { Suspense, useEffect, useState, useCallback } from "react"; -import { - ControlViewQuery, - ControlViewQuery$data, -} from "./__generated__/ControlViewQuery.graphql"; -import { Button } from "@/components/ui/button"; -import { Link, useParams } from "react-router"; -import { LinkIcon, X, Loader2, Search } from "lucide-react"; -import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; -import { Input } from "@/components/ui/input"; -import { useToast } from "@/hooks/use-toast"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { - ControlViewLinkedMitigationsQuery, - ControlViewLinkedMitigationsQuery$data, -} from "./__generated__/ControlViewLinkedMitigationsQuery.graphql"; -import { - ControlViewOrganizationMitigationsQuery, - ControlViewOrganizationMitigationsQuery$data, -} from "./__generated__/ControlViewOrganizationMitigationsQuery.graphql"; +import { Suspense, useEffect } from "react"; +import { ControlViewQuery } from "./__generated__/ControlViewQuery.graphql"; +import { useParams } from "react-router"; +import { Control } from "./Control"; const controlViewQuery = graphql` query ControlViewQuery($controlId: ID!) { node(id: $controlId) { id - ... on Control { - description - name - referenceId - } + ...ControlFragment_Control } } `; -// New query to fetch linked mitigations -const linkedMitigationsQuery = graphql` - query ControlViewLinkedMitigationsQuery($controlId: ID!) { - control: node(id: $controlId) { - id - ... on Control { - mitigations(first: 100) @connection(key: "Control__mitigations") { - edges { - node { - id - name - description - category - importance - state - } - } - } - } - } - } -`; - -// Query to fetch all mitigations for the organization -const organizationMitigationsQuery = graphql` - query ControlViewOrganizationMitigationsQuery($organizationId: ID!) { - organization: node(id: $organizationId) { - id - ... on Organization { - mitigations(first: 100) @connection(key: "Organization__mitigations") { - edges { - node { - id - name - description - category - importance - state - } - } - } - } - } - } -`; - -// Mutation to create a mapping between a control and a mitigation -const createMitigationMappingMutation = graphql` - mutation ControlViewCreateMitigationMappingMutation( - $input: CreateControlMappingInput! - ) { - createControlMapping(input: $input) { - success - } - } -`; - -// Mutation to delete a mapping between a control and a mitigation -const deleteMitigationMappingMutation = graphql` - mutation ControlViewDeleteMitigationMappingMutation( - $input: DeleteControlMappingInput! - ) { - deleteControlMapping(input: $input) { - success - } - } -`; - -export function Control({ - control, -}: { - control: ControlViewQuery$data["node"]; -}) { - const { organizationId /* frameworkId */ } = useParams<{ - organizationId: string; - frameworkId: string; - }>(); - const { toast } = useToast(); - const environment = useRelayEnvironment(); - - // State for mitigation mapping - const [isMitigationMappingDialogOpen, setIsMitigationMappingDialogOpen] = - useState(false); - const [linkedMitigationsData, setLinkedMitigationsData] = - useState(null); - const [organizationMitigationsData, setOrganizationMitigationsData] = - useState(null); - const [mitigationSearchQuery, setMitigationSearchQuery] = useState(""); - const [isLoadingMitigations, setIsLoadingMitigations] = useState(false); - const [isLinkingMitigation, setIsLinkingMitigation] = useState(false); - const [isUnlinkingMitigation, setIsUnlinkingMitigation] = useState(false); - const [categoryFilter, setCategoryFilter] = useState(null); - - // Create mutation hooks - const [commitCreateMitigationMapping] = useMutation( - createMitigationMappingMutation - ); - const [commitDeleteMitigationMapping] = useMutation( - deleteMitigationMappingMutation - ); - - // Load initial linked mitigations data - useEffect(() => { - if (control.id) { - setIsLoadingMitigations(true); - fetchQuery( - environment, - linkedMitigationsQuery, - { - controlId: control.id, - } - ).subscribe({ - next: (data) => { - setLinkedMitigationsData(data); - setIsLoadingMitigations(false); - }, - error: (error: Error) => { - console.error("Error loading initial mitigations:", error); - setIsLoadingMitigations(false); - }, - }); - } - }, [control.id, environment]); - - // Load mitigations data - const loadMitigationsData = useCallback(() => { - if (!organizationId || !control.id) return; - - setIsLoadingMitigations(true); - - // Fetch all mitigations for the organization - fetchQuery( - environment, - organizationMitigationsQuery, - { - organizationId, - } - ).subscribe({ - next: (data) => { - setOrganizationMitigationsData(data); - }, - complete: () => { - // Fetch linked mitigations for this control - fetchQuery( - environment, - linkedMitigationsQuery, - { - controlId: control.id, - } - ).subscribe({ - next: (data) => { - setLinkedMitigationsData(data); - setIsLoadingMitigations(false); - }, - error: (error: Error) => { - console.error("Error fetching linked mitigations:", error); - setIsLoadingMitigations(false); - toast({ - title: "Error", - description: "Failed to load linked mitigations.", - variant: "destructive", - }); - }, - }); - }, - error: (error: Error) => { - console.error("Error fetching organization mitigations:", error); - setIsLoadingMitigations(false); - toast({ - title: "Error", - description: "Failed to load mitigations.", - variant: "destructive", - }); - }, - }); - }, [control.id, environment, organizationId, toast]); - - // Helper functions - const getMitigations = useCallback(() => { - if (!organizationMitigationsData?.organization?.mitigations?.edges) - return []; - return organizationMitigationsData.organization.mitigations.edges.map( - (edge) => edge.node - ); - }, [organizationMitigationsData]); - - const getLinkedMitigations = useCallback(() => { - if (!linkedMitigationsData?.control?.mitigations?.edges) return []; - return linkedMitigationsData.control.mitigations.edges.map( - (edge) => edge.node - ); - }, [linkedMitigationsData]); - - const isMitigationLinked = useCallback( - (mitigationId: string) => { - const linkedMitigations = getLinkedMitigations(); - return linkedMitigations.some( - (mitigation) => mitigation.id === mitigationId - ); - }, - [getLinkedMitigations] - ); - - const getMitigationCategories = useCallback(() => { - const mitigations = getMitigations(); - const categories = new Set(); - - mitigations.forEach((mitigation) => { - if (mitigation.category) { - categories.add(mitigation.category); - } - }); - - return Array.from(categories).sort(); - }, [getMitigations]); - - const filteredMitigations = useCallback(() => { - const mitigations = getMitigations(); - if (!mitigationSearchQuery && !categoryFilter) return mitigations; - - return mitigations.filter((mitigation) => { - // Filter by search query - const matchesSearch = - !mitigationSearchQuery || - mitigation.name - .toLowerCase() - .includes(mitigationSearchQuery.toLowerCase()) || - (mitigation.description && - mitigation.description - .toLowerCase() - .includes(mitigationSearchQuery.toLowerCase())); - - // Filter by category - const matchesCategory = - !categoryFilter || - categoryFilter === "all" || - mitigation.category === categoryFilter; - - return matchesSearch && matchesCategory; - }); - }, [categoryFilter, getMitigations, mitigationSearchQuery]); - - // Handle link/unlink functions - const handleLinkMitigation = useCallback( - (mitigationId: string) => { - if (!control.id) return; - - setIsLinkingMitigation(true); - - commitCreateMitigationMapping({ - variables: { - input: { - controlId: control.id, - mitigationId: mitigationId, - }, - }, - onCompleted: (_, errors) => { - setIsLinkingMitigation(false); - - if (errors) { - console.error("Error linking mitigation:", errors); - toast({ - title: "Error", - description: "Failed to link mitigation. Please try again.", - variant: "destructive", - }); - return; - } - - // Refresh linked mitigations data - fetchQuery( - environment, - linkedMitigationsQuery, - { - controlId: control.id, - } - ).subscribe({ - next: (data) => { - setLinkedMitigationsData(data); - }, - error: (error: Error) => { - console.error("Error refreshing linked mitigations:", error); - }, - }); - - toast({ - title: "Success", - description: "Mitigation successfully linked to control.", - }); - }, - onError: (error) => { - setIsLinkingMitigation(false); - console.error("Error linking mitigation:", error); - toast({ - title: "Error", - description: "Failed to link mitigation. Please try again.", - variant: "destructive", - }); - }, - }); - }, - [commitCreateMitigationMapping, control.id, environment, toast] - ); - - const handleUnlinkMitigation = useCallback( - (mitigationId: string) => { - if (!control.id) return; - - setIsUnlinkingMitigation(true); - - commitDeleteMitigationMapping({ - variables: { - input: { - controlId: control.id, - mitigationId: mitigationId, - }, - }, - onCompleted: (_, errors) => { - setIsUnlinkingMitigation(false); - - if (errors) { - console.error("Error unlinking mitigation:", errors); - toast({ - title: "Error", - description: "Failed to unlink mitigation. Please try again.", - variant: "destructive", - }); - return; - } - - // Refresh linked mitigations data - fetchQuery( - environment, - linkedMitigationsQuery, - { - controlId: control.id, - } - ).subscribe({ - next: (data) => { - setLinkedMitigationsData(data); - }, - error: (error: Error) => { - console.error("Error refreshing linked mitigations:", error); - }, - }); - - toast({ - title: "Success", - description: "Mitigation successfully unlinked from control.", - }); - }, - onError: (error) => { - setIsUnlinkingMitigation(false); - console.error("Error unlinking mitigation:", error); - toast({ - title: "Error", - description: "Failed to unlink mitigation. Please try again.", - variant: "destructive", - }); - }, - }); - }, - [commitDeleteMitigationMapping, control.id, environment, toast] - ); - - const handleOpenMitigationMappingDialog = useCallback(() => { - loadMitigationsData(); - setIsMitigationMappingDialogOpen(true); - }, [loadMitigationsData]); - - // UI helper functions - const formatImportance = (importance: string | undefined): string => { - if (!importance) return "Unknown"; - - switch (importance) { - case "LOW": - return "Low"; - case "MEDIUM": - return "Medium"; - case "HIGH": - return "High"; - case "CRITICAL": - return "Critical"; - default: - return importance; - } - }; - - const formatState = (state: string | undefined): string => { - if (!state) return "Unknown"; - - switch (state) { - case "NOT_STARTED": - return "Not Started"; - case "IN_PROGRESS": - return "In Progress"; - case "IMPLEMENTED": - return "Implemented"; - case "NOT_APPLICABLE": - return "Not Applicable"; - default: - return state; - } - }; - - const getImportanceColor = (importance: string | undefined): string => { - if (!importance) return "bg-gray-100 text-gray-800"; - - switch (importance) { - case "LOW": - return "bg-blue-100 text-blue-800"; - case "MEDIUM": - return "bg-yellow-100 text-yellow-800"; - case "HIGH": - return "bg-orange-100 text-orange-800"; - case "CRITICAL": - return "bg-red-100 text-red-800"; - default: - return "bg-gray-100 text-gray-800"; - } - }; - - const getStateColor = (state: string | undefined): string => { - if (!state) return "bg-gray-100 text-gray-800"; - - switch (state) { - case "NOT_STARTED": - return "bg-gray-100 text-gray-800"; - case "IN_PROGRESS": - return "bg-blue-100 text-blue-800"; - case "IMPLEMENTED": - return "bg-green-100 text-green-800"; - case "NOT_APPLICABLE": - return "bg-purple-100 text-purple-800"; - default: - return "bg-gray-100 text-gray-800"; - } - }; - - return ( -
-
- {control.referenceId} -
-
-

{control.name}

- - {/* Control Description */} - {control.description && ( -
{control.description}
- )} - - {/* Security Measures Section */} -
- {/* Mitigation Mapping Dialog */} - - - - Link Security Measures to Control - - Search and select security measures to link to this control. - This helps track which security measures address this control. - - - -
-
-
- - setMitigationSearchQuery(e.target.value)} - className="w-full pl-10" - /> -
-
-
- -
-
- -
- {isLoadingMitigations ? ( -
- - Loading security measures... -
- ) : ( -
- {filteredMitigations().length === 0 ? ( -
- No security measures found. Try adjusting your search or - select a different category. -
- ) : ( - - - - - - - - - - - {filteredMitigations().map((mitigation) => { - const isLinked = isMitigationLinked(mitigation.id); - return ( - - - - - - - ); - })} - -
Name - Importance - State - Actions -
-
- {mitigation.name} -
- {mitigation.description && ( -
- {mitigation.description} -
- )} -
-
- {formatImportance(mitigation.importance)} -
-
-
- {formatState(mitigation.state)} -
-
- {isLinked ? ( - - ) : ( - - )} -
- )} -
- )} -
- - - - -
-
- - {/* Linked Mitigations List */} -
-
-

- Security measures -

- -
- - {isLoadingMitigations ? ( -
- - Loading security measures... -
- ) : linkedMitigationsData?.control?.mitigations?.edges && - linkedMitigationsData.control.mitigations.edges.length > 0 ? ( -
- - - - - - - - - - - {getLinkedMitigations().map((mitigation) => ( - - - - - - - ))} - -
NameImportanceState - Actions -
-
{mitigation.name}
- {mitigation.description && ( -
- {mitigation.description} -
- )} -
-
- {formatImportance(mitigation.importance)} -
-
-
- {formatState(mitigation.state)} -
-
-
- - -
-
-
- ) : ( -
- No security measures linked to this control yet. Click - "Link Security Measures" to connect some. -
- )} -
-
-
-
- ); -} - function ControlViewContent({ queryRef, }: { queryRef: PreloadedQuery; }) { - const { node: control } = usePreloadedQuery( + const { node } = usePreloadedQuery( controlViewQuery, queryRef ); - return ; + return ; } export default function ControlView({ controlId }: { controlId?: string }) { diff --git a/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlViewCreateMitigationMappingMutation.graphql.ts b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlCreateMitigationMappingMutation.graphql.ts similarity index 63% rename from apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlViewCreateMitigationMappingMutation.graphql.ts rename to apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlCreateMitigationMappingMutation.graphql.ts index 6ae7302e3..210342a59 100644 --- a/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlViewCreateMitigationMappingMutation.graphql.ts +++ b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlCreateMitigationMappingMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<02b5ad33ae08fb81731cf27c34f31ccf>> + * @generated SignedSource<<7598a6cf0314f71bab809d65acc60237>> * @lightSyntaxTransform * @nogrep */ @@ -13,17 +13,17 @@ export type CreateControlMappingInput = { controlId: string; mitigationId: string; }; -export type ControlViewCreateMitigationMappingMutation$variables = { +export type ControlCreateMitigationMappingMutation$variables = { input: CreateControlMappingInput; }; -export type ControlViewCreateMitigationMappingMutation$data = { +export type ControlCreateMitigationMappingMutation$data = { readonly createControlMapping: { readonly success: boolean; }; }; -export type ControlViewCreateMitigationMappingMutation = { - response: ControlViewCreateMitigationMappingMutation$data; - variables: ControlViewCreateMitigationMappingMutation$variables; +export type ControlCreateMitigationMappingMutation = { + response: ControlCreateMitigationMappingMutation$data; + variables: ControlCreateMitigationMappingMutation$variables; }; const node: ConcreteRequest = (function(){ @@ -65,7 +65,7 @@ return { "argumentDefinitions": (v0/*: any*/), "kind": "Fragment", "metadata": null, - "name": "ControlViewCreateMitigationMappingMutation", + "name": "ControlCreateMitigationMappingMutation", "selections": (v1/*: any*/), "type": "Mutation", "abstractKey": null @@ -74,20 +74,20 @@ return { "operation": { "argumentDefinitions": (v0/*: any*/), "kind": "Operation", - "name": "ControlViewCreateMitigationMappingMutation", + "name": "ControlCreateMitigationMappingMutation", "selections": (v1/*: any*/) }, "params": { - "cacheID": "1d95423714868543cd1ae0867f7274f5", + "cacheID": "57ab0bf99dd6ed3d2e980c51f185b9c0", "id": null, "metadata": {}, - "name": "ControlViewCreateMitigationMappingMutation", + "name": "ControlCreateMitigationMappingMutation", "operationKind": "mutation", - "text": "mutation ControlViewCreateMitigationMappingMutation(\n $input: CreateControlMappingInput!\n) {\n createControlMapping(input: $input) {\n success\n }\n}\n" + "text": "mutation ControlCreateMitigationMappingMutation(\n $input: CreateControlMappingInput!\n) {\n createControlMapping(input: $input) {\n success\n }\n}\n" } }; })(); -(node as any).hash = "caa36b4928fc747295d0b3cb8c90f786"; +(node as any).hash = "17ef8f09acabf19168dad896e4225e3d"; export default node; diff --git a/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlViewDeleteMitigationMappingMutation.graphql.ts b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlDeleteMitigationMappingMutation.graphql.ts similarity index 63% rename from apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlViewDeleteMitigationMappingMutation.graphql.ts rename to apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlDeleteMitigationMappingMutation.graphql.ts index 0c5513cec..caf535624 100644 --- a/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlViewDeleteMitigationMappingMutation.graphql.ts +++ b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlDeleteMitigationMappingMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -13,17 +13,17 @@ export type DeleteControlMappingInput = { controlId: string; mitigationId: string; }; -export type ControlViewDeleteMitigationMappingMutation$variables = { +export type ControlDeleteMitigationMappingMutation$variables = { input: DeleteControlMappingInput; }; -export type ControlViewDeleteMitigationMappingMutation$data = { +export type ControlDeleteMitigationMappingMutation$data = { readonly deleteControlMapping: { readonly success: boolean; }; }; -export type ControlViewDeleteMitigationMappingMutation = { - response: ControlViewDeleteMitigationMappingMutation$data; - variables: ControlViewDeleteMitigationMappingMutation$variables; +export type ControlDeleteMitigationMappingMutation = { + response: ControlDeleteMitigationMappingMutation$data; + variables: ControlDeleteMitigationMappingMutation$variables; }; const node: ConcreteRequest = (function(){ @@ -65,7 +65,7 @@ return { "argumentDefinitions": (v0/*: any*/), "kind": "Fragment", "metadata": null, - "name": "ControlViewDeleteMitigationMappingMutation", + "name": "ControlDeleteMitigationMappingMutation", "selections": (v1/*: any*/), "type": "Mutation", "abstractKey": null @@ -74,20 +74,20 @@ return { "operation": { "argumentDefinitions": (v0/*: any*/), "kind": "Operation", - "name": "ControlViewDeleteMitigationMappingMutation", + "name": "ControlDeleteMitigationMappingMutation", "selections": (v1/*: any*/) }, "params": { - "cacheID": "49d66f6dddce3b6c7a82334e36d9b464", + "cacheID": "31dcbb70c0b32e78dcb3fd2a2dc1a58b", "id": null, "metadata": {}, - "name": "ControlViewDeleteMitigationMappingMutation", + "name": "ControlDeleteMitigationMappingMutation", "operationKind": "mutation", - "text": "mutation ControlViewDeleteMitigationMappingMutation(\n $input: DeleteControlMappingInput!\n) {\n deleteControlMapping(input: $input) {\n success\n }\n}\n" + "text": "mutation ControlDeleteMitigationMappingMutation(\n $input: DeleteControlMappingInput!\n) {\n deleteControlMapping(input: $input) {\n success\n }\n}\n" } }; })(); -(node as any).hash = "6f954d3c3c2b38b68a0be5f51de3b935"; +(node as any).hash = "6070b58009682dfa712e445363f0ebf5"; export default node; diff --git a/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlFragment_Control.graphql.ts b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlFragment_Control.graphql.ts new file mode 100644 index 000000000..e00074e67 --- /dev/null +++ b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlFragment_Control.graphql.ts @@ -0,0 +1,66 @@ +/** + * @generated SignedSource<<8c59833cd4588accdf1d316d9e494d26>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type ControlFragment_Control$data = { + readonly description: string; + readonly id: string; + readonly name: string; + readonly referenceId: string; + readonly " $fragmentType": "ControlFragment_Control"; +}; +export type ControlFragment_Control$key = { + readonly " $data"?: ControlFragment_Control$data; + readonly " $fragmentSpreads": FragmentRefs<"ControlFragment_Control">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "ControlFragment_Control", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "description", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "referenceId", + "storageKey": null + } + ], + "type": "Control", + "abstractKey": null +}; + +(node as any).hash = "ccbc9d6743d45b9a4049b661a1f0ad57"; + +export default node; diff --git a/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlViewLinkedMitigationsQuery.graphql.ts b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlLinkedMitigationsQuery.graphql.ts similarity index 84% rename from apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlViewLinkedMitigationsQuery.graphql.ts rename to apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlLinkedMitigationsQuery.graphql.ts index be60b1442..7a46f7671 100644 --- a/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlViewLinkedMitigationsQuery.graphql.ts +++ b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlLinkedMitigationsQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<5a15ae55cd27e88ee96715ba785e412d>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -11,10 +11,10 @@ import { ConcreteRequest } from 'relay-runtime'; export type MitigationImportance = "ADVANCED" | "MANDATORY" | "PREFERRED"; export type MitigationState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED"; -export type ControlViewLinkedMitigationsQuery$variables = { +export type ControlLinkedMitigationsQuery$variables = { controlId: string; }; -export type ControlViewLinkedMitigationsQuery$data = { +export type ControlLinkedMitigationsQuery$data = { readonly control: { readonly id: string; readonly mitigations?: { @@ -31,9 +31,9 @@ export type ControlViewLinkedMitigationsQuery$data = { }; }; }; -export type ControlViewLinkedMitigationsQuery = { - response: ControlViewLinkedMitigationsQuery$data; - variables: ControlViewLinkedMitigationsQuery$variables; +export type ControlLinkedMitigationsQuery = { + response: ControlLinkedMitigationsQuery$data; + variables: ControlLinkedMitigationsQuery$variables; }; const node: ConcreteRequest = (function(){ @@ -170,7 +170,7 @@ return { "argumentDefinitions": (v0/*: any*/), "kind": "Fragment", "metadata": null, - "name": "ControlViewLinkedMitigationsQuery", + "name": "ControlLinkedMitigationsQuery", "selections": [ { "alias": "control", @@ -209,7 +209,7 @@ return { "operation": { "argumentDefinitions": (v0/*: any*/), "kind": "Operation", - "name": "ControlViewLinkedMitigationsQuery", + "name": "ControlLinkedMitigationsQuery", "selections": [ { "alias": "control", @@ -253,7 +253,7 @@ return { ] }, "params": { - "cacheID": "909a089ca2452cc35928267d86d1bb7b", + "cacheID": "082617925f3db2937654823bd7dc8833", "id": null, "metadata": { "connection": [ @@ -268,13 +268,13 @@ return { } ] }, - "name": "ControlViewLinkedMitigationsQuery", + "name": "ControlLinkedMitigationsQuery", "operationKind": "query", - "text": "query ControlViewLinkedMitigationsQuery(\n $controlId: ID!\n) {\n control: node(id: $controlId) {\n __typename\n id\n ... on Control {\n mitigations(first: 100) {\n edges {\n node {\n id\n name\n description\n category\n importance\n state\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n" + "text": "query ControlLinkedMitigationsQuery(\n $controlId: ID!\n) {\n control: node(id: $controlId) {\n __typename\n id\n ... on Control {\n mitigations(first: 100) {\n edges {\n node {\n id\n name\n description\n category\n importance\n state\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n" } }; })(); -(node as any).hash = "c89fb8a01e18c89d2ae310f4c7a2b522"; +(node as any).hash = "3d61e207c8a46a72f19e3414152438b8"; export default node; diff --git a/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlViewOrganizationMitigationsQuery.graphql.ts b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlOrganizationMitigationsQuery.graphql.ts similarity index 83% rename from apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlViewOrganizationMitigationsQuery.graphql.ts rename to apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlOrganizationMitigationsQuery.graphql.ts index fbfee19ef..38789a2e9 100644 --- a/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlViewOrganizationMitigationsQuery.graphql.ts +++ b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlOrganizationMitigationsQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<3c11f3c5fa5a298493ba19d950baa7d2>> * @lightSyntaxTransform * @nogrep */ @@ -11,10 +11,10 @@ import { ConcreteRequest } from 'relay-runtime'; export type MitigationImportance = "ADVANCED" | "MANDATORY" | "PREFERRED"; export type MitigationState = "IMPLEMENTED" | "IN_PROGRESS" | "NOT_APPLICABLE" | "NOT_STARTED"; -export type ControlViewOrganizationMitigationsQuery$variables = { +export type ControlOrganizationMitigationsQuery$variables = { organizationId: string; }; -export type ControlViewOrganizationMitigationsQuery$data = { +export type ControlOrganizationMitigationsQuery$data = { readonly organization: { readonly id: string; readonly mitigations?: { @@ -31,9 +31,9 @@ export type ControlViewOrganizationMitigationsQuery$data = { }; }; }; -export type ControlViewOrganizationMitigationsQuery = { - response: ControlViewOrganizationMitigationsQuery$data; - variables: ControlViewOrganizationMitigationsQuery$variables; +export type ControlOrganizationMitigationsQuery = { + response: ControlOrganizationMitigationsQuery$data; + variables: ControlOrganizationMitigationsQuery$variables; }; const node: ConcreteRequest = (function(){ @@ -170,7 +170,7 @@ return { "argumentDefinitions": (v0/*: any*/), "kind": "Fragment", "metadata": null, - "name": "ControlViewOrganizationMitigationsQuery", + "name": "ControlOrganizationMitigationsQuery", "selections": [ { "alias": "organization", @@ -209,7 +209,7 @@ return { "operation": { "argumentDefinitions": (v0/*: any*/), "kind": "Operation", - "name": "ControlViewOrganizationMitigationsQuery", + "name": "ControlOrganizationMitigationsQuery", "selections": [ { "alias": "organization", @@ -253,7 +253,7 @@ return { ] }, "params": { - "cacheID": "f5c0ad1c64f05306ed6c23a5ad4d649e", + "cacheID": "21e0815f3ed8b62621aa0d7824a93036", "id": null, "metadata": { "connection": [ @@ -268,13 +268,13 @@ return { } ] }, - "name": "ControlViewOrganizationMitigationsQuery", + "name": "ControlOrganizationMitigationsQuery", "operationKind": "query", - "text": "query ControlViewOrganizationMitigationsQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n mitigations(first: 100) {\n edges {\n node {\n id\n name\n description\n category\n importance\n state\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n" + "text": "query ControlOrganizationMitigationsQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ... on Organization {\n mitigations(first: 100) {\n edges {\n node {\n id\n name\n description\n category\n importance\n state\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n }\n}\n" } }; })(); -(node as any).hash = "5c14bf02367f0b6e5ccc633b7a77d1aa"; +(node as any).hash = "54b1b5a6966cf761d49739da3a988db8"; export default node; diff --git a/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlViewQuery.graphql.ts b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlViewQuery.graphql.ts index 13f163e3d..046489dab 100644 --- a/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlViewQuery.graphql.ts +++ b/apps/console/src/pages/organizations/frameworks/controls/__generated__/ControlViewQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<08f7a2f65fb9ae3f6970e76485fa59e4>> + * @generated SignedSource<<2b4d05f2a954b567c9278d604e32db9e>> * @lightSyntaxTransform * @nogrep */ @@ -9,15 +9,14 @@ // @ts-nocheck import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; export type ControlViewQuery$variables = { controlId: string; }; export type ControlViewQuery$data = { readonly node: { - readonly description?: string; readonly id: string; - readonly name?: string; - readonly referenceId?: string; + readonly " $fragmentSpreads": FragmentRefs<"ControlFragment_Control">; }; }; export type ControlViewQuery = { @@ -46,34 +45,6 @@ v2 = { "kind": "ScalarField", "name": "id", "storageKey": null -}, -v3 = { - "kind": "InlineFragment", - "selections": [ - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "description", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "name", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "referenceId", - "storageKey": null - } - ], - "type": "Control", - "abstractKey": null }; return { "fragment": { @@ -91,7 +62,11 @@ return { "plural": false, "selections": [ (v2/*: any*/), - (v3/*: any*/) + { + "args": null, + "kind": "FragmentSpread", + "name": "ControlFragment_Control" + } ], "storageKey": null } @@ -121,23 +96,50 @@ return { "storageKey": null }, (v2/*: any*/), - (v3/*: any*/) + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "description", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "referenceId", + "storageKey": null + } + ], + "type": "Control", + "abstractKey": null + } ], "storageKey": null } ] }, "params": { - "cacheID": "be2e7ad630198d4c5476be3767a45d97", + "cacheID": "a65968fe47c5fb75f72a01d9785ae176", "id": null, "metadata": {}, "name": "ControlViewQuery", "operationKind": "query", - "text": "query ControlViewQuery(\n $controlId: ID!\n) {\n node(id: $controlId) {\n __typename\n id\n ... on Control {\n description\n name\n referenceId\n }\n }\n}\n" + "text": "query ControlViewQuery(\n $controlId: ID!\n) {\n node(id: $controlId) {\n __typename\n id\n ...ControlFragment_Control\n }\n}\n\nfragment ControlFragment_Control on Control {\n id\n description\n name\n referenceId\n}\n" } }; })(); -(node as any).hash = "d692873ef9c85a8f56b8a35e61e5a5bc"; +(node as any).hash = "ea6b1e9919c960be158d2f48aa3b4254"; export default node;