From fbfc3bb2b55c3c0c1c619abb2c390ba5937d37e2 Mon Sep 17 00:00:00 2001 From: Jonathan Date: Fri, 23 May 2025 13:08:33 +0200 Subject: [PATCH] Use loader for relay Signed-off-by: Bryan Frimin Signed-off-by: Sacha Al Himdani --- apps/console2/src/hooks/graph/RiskGraph.ts | 42 ++- .../RiskGraphListQuery.graphql.ts | 313 ++++++++++++++++++ .../RiskGraphNodeQuery.graphql.ts | 249 ++++++++++++++ apps/console2/src/hooks/useDelayedEffect.ts | 19 ++ .../organizations/risks/FormRiskDialog.tsx | 2 +- .../organizations/risks/RiskDetailPage.tsx | 56 +--- .../organizations/risks/RiskOverviewTab.tsx | 30 ++ .../pages/organizations/risks/RisksPage.tsx | 16 +- .../RiskOverviewTabFragment.graphql.ts | 82 +++++ .../console2/src/providers/RelayProviders.tsx | 14 +- apps/console2/src/routes.tsx | 91 +++-- 11 files changed, 829 insertions(+), 85 deletions(-) create mode 100644 apps/console2/src/hooks/graph/__generated__/RiskGraphListQuery.graphql.ts create mode 100644 apps/console2/src/hooks/graph/__generated__/RiskGraphNodeQuery.graphql.ts create mode 100644 apps/console2/src/hooks/useDelayedEffect.ts create mode 100644 apps/console2/src/pages/organizations/risks/RiskOverviewTab.tsx create mode 100644 apps/console2/src/pages/organizations/risks/__generated__/RiskOverviewTabFragment.graphql.ts diff --git a/apps/console2/src/hooks/graph/RiskGraph.ts b/apps/console2/src/hooks/graph/RiskGraph.ts index 90c2b1eb1..107b0afc7 100644 --- a/apps/console2/src/hooks/graph/RiskGraph.ts +++ b/apps/console2/src/hooks/graph/RiskGraph.ts @@ -2,9 +2,14 @@ import { ConnectionHandler, graphql } from "relay-runtime"; import { useTranslate } from "@probo/i18n"; import { useMutationWithToasts } from "../useMutationWithToasts.ts"; import type { RiskGraphDeleteMutation } from "./__generated__/RiskGraphDeleteMutation.graphql.ts"; -import { useLazyLoadQuery, useRefetchableFragment } from "react-relay"; +import { + useLazyLoadQuery, + usePreloadedQuery, + useRefetchableFragment, + type PreloadedQuery, +} from "react-relay"; import { useOrganizationId } from "../useOrganizationId.ts"; -import type { RiskGraphQuery } from "./__generated__/RiskGraphQuery.graphql.ts"; +import type { RiskGraphListQuery } from "./__generated__/RiskGraphListQuery.graphql.ts"; import type { RiskGraphFragment$key } from "./__generated__/RiskGraphFragment.graphql.ts"; const deleteRiskMutation = graphql` @@ -27,8 +32,8 @@ export function useDeleteRiskMutation() { }); } -const risksQuery = graphql` - query RiskGraphQuery($organizationId: ID!) { +export const risksQuery = graphql` + query RiskGraphListQuery($organizationId: ID!) { organization: node(id: $organizationId) { id ...RiskGraphFragment @@ -74,13 +79,11 @@ const risksFragment = graphql` export const RisksConnectionKey = "RisksListQuery_risks"; -export function useRisksQuery() { - const data = useLazyLoadQuery(risksQuery, { - organizationId: useOrganizationId(), - }); +export function useRisksQuery(queryRef: PreloadedQuery) { + const data = usePreloadedQuery(risksQuery, queryRef); const [dataFragment, refetch] = useRefetchableFragment( risksFragment, - data.organization as RiskGraphFragment$key, + data.organization as RiskGraphFragment$key ); const risks = dataFragment?.risks?.edges.map((edge) => edge.node); return { @@ -88,7 +91,26 @@ export function useRisksQuery() { refetch, connectionId: ConnectionHandler.getConnectionID( data.organization.id, - RisksConnectionKey, + RisksConnectionKey ), }; } + +export const riskNodeQuery = graphql` + query RiskGraphNodeQuery($riskId: ID!) { + node(id: $riskId) { + ... on Risk { + name + description + treatment + owner { + id + fullName + } + note + ...useRiskFormFragment + ...RiskOverviewTabFragment + } + } + } +`; diff --git a/apps/console2/src/hooks/graph/__generated__/RiskGraphListQuery.graphql.ts b/apps/console2/src/hooks/graph/__generated__/RiskGraphListQuery.graphql.ts new file mode 100644 index 000000000..412c806ef --- /dev/null +++ b/apps/console2/src/hooks/graph/__generated__/RiskGraphListQuery.graphql.ts @@ -0,0 +1,313 @@ +/** + * @generated SignedSource<<8ae6d82b30ec5657f8a55bd47fab4e7e>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type RiskGraphListQuery$variables = { + organizationId: string; +}; +export type RiskGraphListQuery$data = { + readonly organization: { + readonly id: string; + readonly " $fragmentSpreads": FragmentRefs<"RiskGraphFragment">; + }; +}; +export type RiskGraphListQuery = { + response: RiskGraphListQuery$data; + variables: RiskGraphListQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "organizationId" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "organizationId" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v4 = [ + { + "kind": "Literal", + "name": "first", + "value": 50 + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "RiskGraphListQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "args": null, + "kind": "FragmentSpread", + "name": "RiskGraphFragment" + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "RiskGraphListQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": (v4/*: any*/), + "concreteType": "RiskConnection", + "kind": "LinkedField", + "name": "risks", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "RiskEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Risk", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "category", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "treatment", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "inherentLikelihood", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "inherentImpact", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "residualLikelihood", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "residualImpact", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "inherentRiskScore", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "residualRiskScore", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "description", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "note", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "People", + "kind": "LinkedField", + "name": "owner", + "plural": false, + "selections": [ + (v2/*: any*/) + ], + "storageKey": null + }, + (v3/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasPreviousPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "startCursor", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "risks(first:50)" + }, + { + "alias": null, + "args": (v4/*: any*/), + "filters": [ + "orderBy" + ], + "handle": "connection", + "key": "RisksListQuery_risks", + "kind": "LinkedHandle", + "name": "risks" + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "36e237e142f201c74ebc23ff46d7345a", + "id": null, + "metadata": {}, + "name": "RiskGraphListQuery", + "operationKind": "query", + "text": "query RiskGraphListQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ...RiskGraphFragment\n }\n}\n\nfragment RiskGraphFragment on Organization {\n risks(first: 50) {\n edges {\n node {\n id\n name\n category\n treatment\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n inherentRiskScore\n residualRiskScore\n ...useRiskFormFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\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" + } +}; +})(); + +(node as any).hash = "5d41ad11b598f63829f481ff0ae8aec7"; + +export default node; diff --git a/apps/console2/src/hooks/graph/__generated__/RiskGraphNodeQuery.graphql.ts b/apps/console2/src/hooks/graph/__generated__/RiskGraphNodeQuery.graphql.ts new file mode 100644 index 000000000..d11ee84f8 --- /dev/null +++ b/apps/console2/src/hooks/graph/__generated__/RiskGraphNodeQuery.graphql.ts @@ -0,0 +1,249 @@ +/** + * @generated SignedSource<<6efbbaf2cb9056e26a38c89835260547>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type RiskTreatment = "ACCEPTED" | "AVOIDED" | "MITIGATED" | "TRANSFERRED" | "%future added value"; +export type RiskGraphNodeQuery$variables = { + riskId: string; +}; +export type RiskGraphNodeQuery$data = { + readonly node: { + readonly description?: string; + readonly name?: string; + readonly note?: string; + readonly owner?: { + readonly fullName: string; + readonly id: string; + } | null | undefined; + readonly treatment?: RiskTreatment; + readonly " $fragmentSpreads": FragmentRefs<"RiskOverviewTabFragment" | "useRiskFormFragment">; + }; +}; +export type RiskGraphNodeQuery = { + response: RiskGraphNodeQuery$data; + variables: RiskGraphNodeQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "riskId" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "riskId" + } +], +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": "treatment", + "storageKey": null +}, +v5 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v6 = { + "alias": null, + "args": null, + "concreteType": "People", + "kind": "LinkedField", + "name": "owner", + "plural": false, + "selections": [ + (v5/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "storageKey": null +}, +v7 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "note", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "RiskGraphNodeQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "kind": "InlineFragment", + "selections": [ + (v2/*: any*/), + (v3/*: any*/), + (v4/*: any*/), + (v6/*: any*/), + (v7/*: any*/), + { + "args": null, + "kind": "FragmentSpread", + "name": "useRiskFormFragment" + }, + { + "args": null, + "kind": "FragmentSpread", + "name": "RiskOverviewTabFragment" + } + ], + "type": "Risk", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "RiskGraphNodeQuery", + "selections": [ + { + "alias": null, + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + }, + (v5/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + (v2/*: any*/), + (v3/*: any*/), + (v4/*: any*/), + (v6/*: any*/), + (v7/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "category", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "inherentLikelihood", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "inherentImpact", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "residualLikelihood", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "residualImpact", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "inherentRiskScore", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "residualRiskScore", + "storageKey": null + } + ], + "type": "Risk", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "f3e35dd3d4ea0933858541184c664b4d", + "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 }\n id\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" + } +}; +})(); + +(node as any).hash = "76afe5fc70188381350154bdba7aa066"; + +export default node; diff --git a/apps/console2/src/hooks/useDelayedEffect.ts b/apps/console2/src/hooks/useDelayedEffect.ts new file mode 100644 index 000000000..ee421db8a --- /dev/null +++ b/apps/console2/src/hooks/useDelayedEffect.ts @@ -0,0 +1,19 @@ +import { useEffect, useRef } from "react"; + +/** + * Hook to handle cleanup after a delay + * + * Used for disposing the graphQL query when the component unmounts + */ +export function useCleanup(callback: () => void, delay: number) { + const timer = useRef>(null); + + useEffect(() => { + if (timer.current) { + clearTimeout(timer.current); + } + return () => { + timer.current = setTimeout(callback, delay); + }; + }, [callback, delay]); +} diff --git a/apps/console2/src/pages/organizations/risks/FormRiskDialog.tsx b/apps/console2/src/pages/organizations/risks/FormRiskDialog.tsx index 6a71bb4a8..6c0f91f77 100644 --- a/apps/console2/src/pages/organizations/risks/FormRiskDialog.tsx +++ b/apps/console2/src/pages/organizations/risks/FormRiskDialog.tsx @@ -36,7 +36,7 @@ type Props = { open?: boolean; risk?: RiskKey; onSuccess?: () => void; - connection: string; + connection?: string; }; type RiskTemplate = { diff --git a/apps/console2/src/pages/organizations/risks/RiskDetailPage.tsx b/apps/console2/src/pages/organizations/risks/RiskDetailPage.tsx index 891edc0ba..608f60667 100644 --- a/apps/console2/src/pages/organizations/risks/RiskDetailPage.tsx +++ b/apps/console2/src/pages/organizations/risks/RiskDetailPage.tsx @@ -11,50 +11,27 @@ import { IconTrashCan, PageHeader, PropertyRow, - RiskOverview, } from "@probo/ui"; -import { useNavigate, useParams } from "react-router"; +import { Outlet, useNavigate, useParams } from "react-router"; import { useTranslate } from "@probo/i18n"; import { getTreatment, sprintf } from "@probo/helpers"; -import { ConnectionHandler, graphql } from "relay-runtime"; -import { useLazyLoadQuery } from "react-relay"; -import type { - RiskDetailPageQuery, - RiskDetailPageQuery$data, -} from "./__generated__/RiskDetailPageQuery.graphql"; +import { ConnectionHandler } from "relay-runtime"; +import { usePreloadedQuery, type PreloadedQuery } from "react-relay"; import FormRiskDialog from "./FormRiskDialog"; import { usePageTitle } from "@probo/hooks"; import { useOrganizationId } from "../../../hooks/useOrganizationId"; import { + riskNodeQuery, RisksConnectionKey, useDeleteRiskMutation, } from "../../../hooks/graph/RiskGraph"; +import type { RiskGraphNodeQuery } from "../../../hooks/graph/__generated__/RiskGraphNodeQuery.graphql"; -const riskQuery = graphql` - query RiskDetailPageQuery($riskId: ID!) { - node(id: $riskId) { - ... on Risk { - name - description - treatment - owner { - id - fullName - } - inherentLikelihood - inherentImpact - residualLikelihood - residualImpact - note - createdAt - updatedAt - ...useRiskFormFragment - } - } - } -`; +type Props = { + queryRef: PreloadedQuery; +}; -export default function RiskDetailPage() { +export default function RiskDetailPage(props: Props) { const { riskId } = useParams<{ riskId: string }>(); const organizationId = useOrganizationId(); const navigate = useNavigate(); @@ -64,8 +41,8 @@ export default function RiskDetailPage() { } const { __ } = useTranslate(); - const data = useLazyLoadQuery(riskQuery, { riskId }); - const risk = data.node as Required; + const data = usePreloadedQuery(riskNodeQuery, props.queryRef); + const risk = data.node; const [deleteRisk] = useDeleteRiskMutation(); usePageTitle(risk.name ?? "Risk detail"); @@ -73,7 +50,7 @@ export default function RiskDetailPage() { const onDelete = (riskId: string) => { const connectionId = ConnectionHandler.getConnectionID( organizationId, - RisksConnectionKey, + RisksConnectionKey ); deleteRisk({ variables: { @@ -114,9 +91,9 @@ export default function RiskDetailPage() { onDelete(riskId)} > @@ -130,10 +107,7 @@ export default function RiskDetailPage() { -
- - -
+ diff --git a/apps/console2/src/pages/organizations/risks/RiskOverviewTab.tsx b/apps/console2/src/pages/organizations/risks/RiskOverviewTab.tsx new file mode 100644 index 000000000..e7735f792 --- /dev/null +++ b/apps/console2/src/pages/organizations/risks/RiskOverviewTab.tsx @@ -0,0 +1,30 @@ +import { useFragment } from "react-relay"; +import { useOutletContext } from "react-router"; +import { graphql } from "relay-runtime"; +import type { RiskOverviewTabFragment$key } from "./__generated__/RiskOverviewTabFragment.graphql"; +import { RiskOverview } from "@probo/ui"; + +const overviewFragment = graphql` + fragment RiskOverviewTabFragment on Risk { + inherentLikelihood + inherentImpact + residualLikelihood + residualImpact + inherentRiskScore + residualRiskScore + } +`; + +export default function RiskOverviewTab() { + const { risk: key } = useOutletContext<{ + risk: RiskOverviewTabFragment$key; + }>(); + + const risk = useFragment(overviewFragment, key); + return ( +
+ + +
+ ); +} diff --git a/apps/console2/src/pages/organizations/risks/RisksPage.tsx b/apps/console2/src/pages/organizations/risks/RisksPage.tsx index 7ad25da4b..6cb646908 100644 --- a/apps/console2/src/pages/organizations/risks/RisksPage.tsx +++ b/apps/console2/src/pages/organizations/risks/RisksPage.tsx @@ -27,16 +27,22 @@ import { useRisksQuery, } from "../../../hooks/graph/RiskGraph"; import { SortableTable, SortableTh } from "../../../components/SortableTable"; +import type { PreloadedQuery } from "react-relay"; +import type { RiskGraphQuery } from "../../../hooks/graph/__generated__/RiskGraphQuery.graphql"; -export default function RisksPage() { +type Props = { + queryRef: PreloadedQuery; +}; + +export default function RisksPage(props: Props) { const { __ } = useTranslate(); const [editedRisk, setEditedRisk] = useState | null>( - null, + null ); const organizationId = useOrganizationId(); const [deleteRisk] = useDeleteRiskMutation(); - const { connectionId, risks, refetch } = useRisksQuery(); + const { connectionId, risks, refetch } = useRisksQuery(props.queryRef); const onDelete = (riskId: string) => { deleteRisk({ @@ -119,9 +125,9 @@ export default function RisksPage() { onDelete(risk.id)} > diff --git a/apps/console2/src/pages/organizations/risks/__generated__/RiskOverviewTabFragment.graphql.ts b/apps/console2/src/pages/organizations/risks/__generated__/RiskOverviewTabFragment.graphql.ts new file mode 100644 index 000000000..90f282e62 --- /dev/null +++ b/apps/console2/src/pages/organizations/risks/__generated__/RiskOverviewTabFragment.graphql.ts @@ -0,0 +1,82 @@ +/** + * @generated SignedSource<<2b276a39b8020e7be036acb6fe4ca077>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type RiskOverviewTabFragment$data = { + readonly inherentImpact: number; + readonly inherentLikelihood: number; + readonly inherentRiskScore: number; + readonly residualImpact: number; + readonly residualLikelihood: number; + readonly residualRiskScore: number; + readonly " $fragmentType": "RiskOverviewTabFragment"; +}; +export type RiskOverviewTabFragment$key = { + readonly " $data"?: RiskOverviewTabFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"RiskOverviewTabFragment">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "RiskOverviewTabFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "inherentLikelihood", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "inherentImpact", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "residualLikelihood", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "residualImpact", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "inherentRiskScore", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "residualRiskScore", + "storageKey": null + } + ], + "type": "Risk", + "abstractKey": null +}; + +(node as any).hash = "ba472e37a44c06c48847501204080fb6"; + +export default node; diff --git a/apps/console2/src/providers/RelayProviders.tsx b/apps/console2/src/providers/RelayProviders.tsx index c5e89f721..49b92fae1 100644 --- a/apps/console2/src/providers/RelayProviders.tsx +++ b/apps/console2/src/providers/RelayProviders.tsx @@ -30,7 +30,7 @@ const fetchRelay: FetchFunction = async ( request, variables, _, - uploadables + uploadables, ) => { const requestInit: RequestInit = { method: "POST", @@ -46,7 +46,7 @@ const fetchRelay: FetchFunction = async ( operationName: request.name, query: request.text, variables: variables, - }) + }), ); const uploadableMap: { @@ -80,7 +80,7 @@ const fetchRelay: FetchFunction = async ( const response = await fetch( import.meta.env.VITE_API_URL + "/api/console/v1/query", - requestInit + requestInit, ); if (response.status === 500) { @@ -100,8 +100,8 @@ const fetchRelay: FetchFunction = async ( `Error fetching GraphQL query '${ request.name }' with variables '${JSON.stringify(variables)}': ${JSON.stringify( - json.errors - )}` + json.errors, + )}`, ); } @@ -114,7 +114,7 @@ const store = new Store(source, { gcReleaseBufferSize: 20, }); -const environment = new Environment({ +export const relayEnvironment = new Environment({ network: Network.create(fetchRelay), store, }); @@ -124,7 +124,7 @@ const environment = new Environment({ */ export const RelayProvider = ({ children }: PropsWithChildren) => { return ( - + {children} ); diff --git a/apps/console2/src/routes.tsx b/apps/console2/src/routes.tsx index c58be039b..75fb8dc41 100644 --- a/apps/console2/src/routes.tsx +++ b/apps/console2/src/routes.tsx @@ -1,14 +1,27 @@ import { createBrowserRouter, + useLoaderData, useRouteError, type RouteObject, } from "react-router"; import { MainLayout } from "./layouts/MainLayout"; import { AuthLayout, CenteredLayout, CenteredLayoutSkeleton } from "@probo/ui"; -import { lazy, Suspense, type FC } from "react"; -import { UnAuthenticatedError } from "./providers/RelayProviders"; +import { + lazy, + Suspense, + useEffect, + type FC, + type LazyExoticComponent, +} from "react"; +import { + relayEnvironment, + UnAuthenticatedError, +} from "./providers/RelayProviders"; import { RisksPageSkeleton } from "./components/skeletons/RisksPageSkeleton.tsx"; import { PageSkeleton } from "./components/skeletons/PageSkeleton.tsx"; +import { loadQuery, type PreloadedQuery } from "react-relay"; +import { riskNodeQuery, risksQuery } from "./hooks/graph/RiskGraph.ts"; +import { useCleanup } from "./hooks/useDelayedEffect.ts"; function ErrorBoundary() { const error = useRouteError(); @@ -20,12 +33,11 @@ function ErrorBoundary() { } type Route = { - path: string; - Component: FC; + Component: FC | LazyExoticComponent>; children?: Route[]; - ErrorBoundary?: FC; fallback?: FC; -}; + queryLoader?: (params: Record) => PreloadedQuery; +} & Omit; const routes = [ { @@ -50,7 +62,7 @@ const routes = [ { path: "organizations/new", Component: lazy( - () => import("./pages/organizations/NewOrganizationPage") + () => import("./pages/organizations/NewOrganizationPage"), ), }, ], @@ -64,20 +76,32 @@ const routes = [ path: "vendors", fallback: PageSkeleton, Component: lazy( - () => import("./pages/organizations/vendors/VendorsPage") + () => import("./pages/organizations/vendors/VendorsPage"), ), }, { path: "risks", fallback: RisksPageSkeleton, + queryLoader: ({ organizationId }) => + loadQuery(relayEnvironment, risksQuery, { organizationId }), Component: lazy(() => import("./pages/organizations/risks/RisksPage")), }, { path: "risks/:riskId", fallback: PageSkeleton, + queryLoader: ({ riskId }) => + loadQuery(relayEnvironment, riskNodeQuery, { riskId }), Component: lazy( - () => import("./pages/organizations/risks/RiskDetailPage") + () => import("./pages/organizations/risks/RiskDetailPage"), ), + children: [ + { + path: "", + Component: lazy( + () => import("./pages/organizations/risks/RiskOverviewTab"), + ), + }, + ], }, ], }, @@ -86,23 +110,48 @@ const routes = [ /** * Wrap component with a suspense to handle lazy loading & relay loading states */ -function routeTransformer(route: Route): RouteObject { - if ("fallback" in route && route.fallback) { - const fallback = ; - return { - ...route, - children: route.children?.map(routeTransformer), - Component: () => ( - - +function routeTransformer({ + fallback: FallbackComponent, + queryLoader, + ...route +}: Route): RouteObject { + let result = { ...route }; + if (FallbackComponent) { + result = { + ...result, + Component: (props) => ( + }> + ), - } as RouteObject; + }; + } + if (queryLoader) { + result = { + ...result, + loader: ({ params }) => { + const query = queryLoader(params as Record); + return { + queryRef: query, + dispose: () => { + console.log("cleaning up query"); + query.dispose(); + }, + }; + }, + Component: () => { + const { queryRef, dispose } = useLoaderData(); + + useCleanup(dispose, 1000); + + return ; + }, + }; } return { - ...route, + ...result, children: route.children?.map(routeTransformer), - }; + } as RouteObject; } export const router = createBrowserRouter(routes.map(routeTransformer));