Order risks by owner name

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-09-18 13:20:26 +02:00
parent eabc34c3ac
commit 72a581b66b
16 changed files with 272 additions and 64 deletions

View File

@@ -21,7 +21,7 @@ type Order = {
field: string; field: string;
}; };
const SortableContext = createContext({ export const SortableContext = createContext({
order: { order: {
direction: "DESC", direction: "DESC",
field: "CREATED_AT", field: "CREATED_AT",

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<38b7fae585eb2c09409935db8ad3cfe9>> * @generated SignedSource<<828d1e2364b7b8c540508d9fb7104b06>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -17,6 +17,7 @@ export type useRiskFormFragment$data = {
readonly id: string; readonly id: string;
readonly inherentImpact: number; readonly inherentImpact: number;
readonly inherentLikelihood: number; readonly inherentLikelihood: number;
readonly inherentRiskScore: number;
readonly name: string; readonly name: string;
readonly note: string; readonly note: string;
readonly owner: { readonly owner: {
@@ -24,6 +25,7 @@ export type useRiskFormFragment$data = {
} | null | undefined; } | null | undefined;
readonly residualImpact: number; readonly residualImpact: number;
readonly residualLikelihood: number; readonly residualLikelihood: number;
readonly residualRiskScore: number;
readonly treatment: RiskTreatment; readonly treatment: RiskTreatment;
readonly " $fragmentType": "useRiskFormFragment"; readonly " $fragmentType": "useRiskFormFragment";
}; };
@@ -103,6 +105,20 @@ return {
"name": "residualImpact", "name": "residualImpact",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "inherentRiskScore",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualRiskScore",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -128,6 +144,6 @@ return {
}; };
})(); })();
(node as any).hash = "7ad9386b4049af3f139d7269bb5229c2"; (node as any).hash = "d9b52f879eadba5f7b1c18dcdbf85559";
export default node; export default node;

View File

@@ -18,6 +18,8 @@ const RiskFragment = graphql`
inherentImpact inherentImpact
residualLikelihood residualLikelihood
residualImpact residualImpact
inherentRiskScore
residualRiskScore
note note
owner { owner {
id id

View File

@@ -1,10 +1,10 @@
import { ConnectionHandler, graphql } from "relay-runtime"; import { graphql } from "relay-runtime";
import { useTranslate } from "@probo/i18n"; import { useTranslate } from "@probo/i18n";
import { useMutationWithToasts } from "../useMutationWithToasts.ts"; import { useMutationWithToasts } from "../useMutationWithToasts.ts";
import type { RiskGraphDeleteMutation } from "./__generated__/RiskGraphDeleteMutation.graphql.ts"; import type { RiskGraphDeleteMutation } from "./__generated__/RiskGraphDeleteMutation.graphql.ts";
import { import {
usePreloadedQuery, usePreloadedQuery,
useRefetchableFragment, usePaginationFragment,
type PreloadedQuery, type PreloadedQuery,
} from "react-relay"; } from "react-relay";
import type { RiskGraphListQuery } from "./__generated__/RiskGraphListQuery.graphql.ts"; import type { RiskGraphListQuery } from "./__generated__/RiskGraphListQuery.graphql.ts";
@@ -44,7 +44,7 @@ const risksFragment = graphql`
@refetchable(queryName: "RisksListQuery") @refetchable(queryName: "RisksListQuery")
@argumentDefinitions( @argumentDefinitions(
first: { type: "Int", defaultValue: 50 } first: { type: "Int", defaultValue: 50 }
order: { type: "RiskOrder", defaultValue: null } order: { type: "RiskOrder", defaultValue: { direction: DESC, field: CREATED_AT } }
after: { type: "CursorKey", defaultValue: null } after: { type: "CursorKey", defaultValue: null }
before: { type: "CursorKey", defaultValue: null } before: { type: "CursorKey", defaultValue: null }
last: { type: "Int", defaultValue: null } last: { type: "Int", defaultValue: null }
@@ -58,6 +58,7 @@ const risksFragment = graphql`
orderBy: $order orderBy: $order
filter: { snapshotId: $snapshotId } filter: { snapshotId: $snapshotId }
) @connection(key: "RisksListQuery_risks", filters: ["filter"]) { ) @connection(key: "RisksListQuery_risks", filters: ["filter"]) {
__id
edges { edges {
node { node {
id id
@@ -65,6 +66,10 @@ const risksFragment = graphql`
name name
category category
treatment treatment
owner {
id
fullName
}
inherentLikelihood inherentLikelihood
inherentImpact inherentImpact
residualLikelihood residualLikelihood
@@ -80,21 +85,18 @@ const risksFragment = graphql`
export const RisksConnectionKey = "RisksListQuery_risks"; export const RisksConnectionKey = "RisksListQuery_risks";
export function useRisksQuery(queryRef: PreloadedQuery<RiskGraphListQuery>, snapshotId?: string | null) { export function useRisksQuery(queryRef: PreloadedQuery<RiskGraphListQuery>) {
const data = usePreloadedQuery(risksQuery, queryRef); const data = usePreloadedQuery(risksQuery, queryRef);
const [dataFragment, refetch] = useRefetchableFragment( const pagination = usePaginationFragment(
risksFragment, risksFragment,
data.organization as RiskGraphFragment$key, data.organization as RiskGraphFragment$key,
); );
const risks = dataFragment?.risks?.edges.map((edge) => edge.node); const risks = pagination.data?.risks?.edges.map((edge) => edge.node);
return { return {
...pagination,
risks, risks,
refetch, connectionId: pagination.data.risks.__id,
connectionId: ConnectionHandler.getConnectionID(
data.organization.id,
RisksConnectionKey,
{ filter: { snapshotId: snapshotId || null } }
),
}; };
} }

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<2b964bdee12b49230932678f583c7401>> * @generated SignedSource<<c579c68998b3ded3e8833a7911f01a19>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -14,6 +14,7 @@ import { FragmentRefs } from "relay-runtime";
export type RiskGraphFragment$data = { export type RiskGraphFragment$data = {
readonly id: string; readonly id: string;
readonly risks: { readonly risks: {
readonly __id: string;
readonly edges: ReadonlyArray<{ readonly edges: ReadonlyArray<{
readonly node: { readonly node: {
readonly category: string; readonly category: string;
@@ -22,6 +23,10 @@ export type RiskGraphFragment$data = {
readonly inherentLikelihood: number; readonly inherentLikelihood: number;
readonly inherentRiskScore: number; readonly inherentRiskScore: number;
readonly name: string; readonly name: string;
readonly owner: {
readonly fullName: string;
readonly id: string;
} | null | undefined;
readonly residualImpact: number; readonly residualImpact: number;
readonly residualLikelihood: number; readonly residualLikelihood: number;
readonly residualRiskScore: number; readonly residualRiskScore: number;
@@ -74,7 +79,10 @@ return {
"name": "last" "name": "last"
}, },
{ {
"defaultValue": null, "defaultValue": {
"direction": "DESC",
"field": "CREATED_AT"
},
"kind": "LocalArgument", "kind": "LocalArgument",
"name": "order" "name": "order"
}, },
@@ -183,6 +191,25 @@ return {
"name": "treatment", "name": "treatment",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
}
],
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -288,6 +315,18 @@ return {
} }
], ],
"storageKey": null "storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
} }
], ],
"storageKey": null "storageKey": null
@@ -299,6 +338,6 @@ return {
}; };
})(); })();
(node as any).hash = "95c68a022cbf075eede9b7417393a369"; (node as any).hash = "d8048ddb0ff8d4b08b7b9676d9b98f1a";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<af89b7b79cdb083497eabebd82b51991>> * @generated SignedSource<<af4a12179ab70cae3cc3f8c434151de0>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -76,6 +76,14 @@ v5 = [
"kind": "Literal", "kind": "Literal",
"name": "first", "name": "first",
"value": 50 "value": 50
},
{
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "DESC",
"field": "CREATED_AT"
}
} }
]; ];
return { return {
@@ -178,6 +186,25 @@ return {
"name": "treatment", "name": "treatment",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
}
],
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -234,18 +261,6 @@ return {
"name": "note", "name": "note",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v2/*: any*/)
],
"storageKey": null
},
(v4/*: any*/) (v4/*: any*/)
], ],
"storageKey": null "storageKey": null
@@ -298,6 +313,18 @@ return {
} }
], ],
"storageKey": null "storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
} }
], ],
"storageKey": null "storageKey": null
@@ -323,12 +350,12 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "b2114c476ed7e095fc9d2317b6b1c554", "cacheID": "225123bb702f2bce307ca69bea183e88",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "RiskGraphListQuery", "name": "RiskGraphListQuery",
"operationKind": "query", "operationKind": "query",
"text": "query RiskGraphListQuery(\n $organizationId: ID!\n $snapshotId: ID\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ...RiskGraphFragment_3iomuz\n }\n}\n\nfragment RiskGraphFragment_3iomuz on Organization {\n risks(first: 50, filter: {snapshotId: $snapshotId}) {\n edges {\n node {\n id\n snapshotId\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" "text": "query RiskGraphListQuery(\n $organizationId: ID!\n $snapshotId: ID\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ...RiskGraphFragment_3iomuz\n }\n}\n\nfragment RiskGraphFragment_3iomuz on Organization {\n risks(first: 50, orderBy: {direction: DESC, field: CREATED_AT}, filter: {snapshotId: $snapshotId}) {\n edges {\n node {\n id\n snapshotId\n name\n category\n treatment\n owner {\n id\n fullName\n }\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 inherentRiskScore\n residualRiskScore\n note\n owner {\n id\n }\n}\n"
} }
}; };
})(); })();

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<494ff8512e4a50c2bbc2cb3453dbab1c>> * @generated SignedSource<<9abcf06eed9cb70f62b2aed2b907e9e5>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -655,12 +655,12 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "03ef938803c52b572e23e691d50c4188", "cacheID": "c3d393180d6273b2e910c7ebffb75df7",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "RiskGraphNodeQuery", "name": "RiskGraphNodeQuery",
"operationKind": "query", "operationKind": "query",
"text": "query RiskGraphNodeQuery(\n $riskId: ID!\n) {\n node(id: $riskId) {\n __typename\n ... on Risk {\n id\n snapshotId\n name\n description\n treatment\n owner {\n id\n fullName\n }\n note\n inherentRiskScore\n residualRiskScore\n measuresInfo: measures(first: 0) {\n totalCount\n }\n documentsInfo: documents(first: 0) {\n totalCount\n }\n controlsInfo: controls(first: 0) {\n totalCount\n }\n ...useRiskFormFragment\n ...RiskOverviewTabFragment\n ...RiskMeasuresTabFragment\n ...RiskDocumentsTabFragment\n ...RiskControlsTabFragment\n }\n id\n }\n}\n\nfragment LinkedDocumentsCardFragment on Document {\n id\n title\n createdAt\n documentType\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n\nfragment RiskControlsTabFragment on Risk {\n id\n controls(first: 20) {\n edges {\n node {\n id\n sectionTitle\n name\n framework {\n id\n name\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment RiskDocumentsTabFragment on Risk {\n id\n documents(first: 100) {\n edges {\n node {\n id\n ...LinkedDocumentsCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment RiskMeasuresTabFragment on Risk {\n id\n measures(first: 100) {\n edges {\n node {\n id\n ...LinkedMeasuresCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment RiskOverviewTabFragment on Risk {\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n inherentRiskScore\n residualRiskScore\n}\n\nfragment useRiskFormFragment on Risk {\n id\n name\n category\n description\n treatment\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n note\n owner {\n id\n }\n}\n" "text": "query RiskGraphNodeQuery(\n $riskId: ID!\n) {\n node(id: $riskId) {\n __typename\n ... on Risk {\n id\n snapshotId\n name\n description\n treatment\n owner {\n id\n fullName\n }\n note\n inherentRiskScore\n residualRiskScore\n measuresInfo: measures(first: 0) {\n totalCount\n }\n documentsInfo: documents(first: 0) {\n totalCount\n }\n controlsInfo: controls(first: 0) {\n totalCount\n }\n ...useRiskFormFragment\n ...RiskOverviewTabFragment\n ...RiskMeasuresTabFragment\n ...RiskDocumentsTabFragment\n ...RiskControlsTabFragment\n }\n id\n }\n}\n\nfragment LinkedDocumentsCardFragment on Document {\n id\n title\n createdAt\n documentType\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n\nfragment LinkedMeasuresCardFragment on Measure {\n id\n name\n state\n}\n\nfragment RiskControlsTabFragment on Risk {\n id\n controls(first: 20) {\n edges {\n node {\n id\n sectionTitle\n name\n framework {\n id\n name\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment RiskDocumentsTabFragment on Risk {\n id\n documents(first: 100) {\n edges {\n node {\n id\n ...LinkedDocumentsCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment RiskMeasuresTabFragment on Risk {\n id\n measures(first: 100) {\n edges {\n node {\n id\n ...LinkedMeasuresCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n\nfragment RiskOverviewTabFragment on Risk {\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n inherentRiskScore\n residualRiskScore\n}\n\nfragment useRiskFormFragment on Risk {\n id\n name\n category\n description\n treatment\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n inherentRiskScore\n residualRiskScore\n note\n owner {\n id\n }\n}\n"
} }
}; };
})(); })();

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<4a0922eabd4e20c574db91e75f45a562>> * @generated SignedSource<<29125e9f2562c96a6076ed8b0504a4b1>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -11,7 +11,7 @@
import { ConcreteRequest } from 'relay-runtime'; import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime"; import { FragmentRefs } from "relay-runtime";
export type OrderDirection = "ASC" | "DESC"; export type OrderDirection = "ASC" | "DESC";
export type RiskOrderField = "CATEGORY" | "CREATED_AT" | "INHERENT_RISK_SCORE" | "NAME" | "RESIDUAL_RISK_SCORE" | "TREATMENT" | "UPDATED_AT"; export type RiskOrderField = "CATEGORY" | "CREATED_AT" | "INHERENT_RISK_SCORE" | "NAME" | "OWNER_FULL_NAME" | "RESIDUAL_RISK_SCORE" | "TREATMENT" | "UPDATED_AT";
export type RiskOrder = { export type RiskOrder = {
direction: OrderDirection; direction: OrderDirection;
field: RiskOrderField; field: RiskOrderField;
@@ -62,7 +62,10 @@ v4 = {
"name": "last" "name": "last"
}, },
v5 = { v5 = {
"defaultValue": null, "defaultValue": {
"direction": "DESC",
"field": "CREATED_AT"
},
"kind": "LocalArgument", "kind": "LocalArgument",
"name": "order" "name": "order"
}, },
@@ -261,6 +264,25 @@ return {
"name": "treatment", "name": "treatment",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v14/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
}
],
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -317,18 +339,6 @@ return {
"name": "note", "name": "note",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"concreteType": "People",
"kind": "LinkedField",
"name": "owner",
"plural": false,
"selections": [
(v14/*: any*/)
],
"storageKey": null
},
(v13/*: any*/) (v13/*: any*/)
], ],
"storageKey": null "storageKey": null
@@ -381,6 +391,18 @@ return {
} }
], ],
"storageKey": null "storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
} }
], ],
"storageKey": null "storageKey": null
@@ -406,16 +428,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "161eb78d1d5100120c68db5d8106aa7e", "cacheID": "34a33dde7e4e7868a885b6b6317f506b",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "RisksListQuery", "name": "RisksListQuery",
"operationKind": "query", "operationKind": "query",
"text": "query RisksListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 50\n $last: Int = null\n $order: RiskOrder = null\n $snapshotId: ID = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...RiskGraphFragment_25MC8O\n id\n }\n}\n\nfragment RiskGraphFragment_25MC8O on Organization {\n risks(first: $first, after: $after, last: $last, before: $before, orderBy: $order, filter: {snapshotId: $snapshotId}) {\n edges {\n node {\n id\n snapshotId\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" "text": "query RisksListQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 50\n $last: Int = null\n $order: RiskOrder = {direction: DESC, field: CREATED_AT}\n $snapshotId: ID = null\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...RiskGraphFragment_25MC8O\n id\n }\n}\n\nfragment RiskGraphFragment_25MC8O on Organization {\n risks(first: $first, after: $after, last: $last, before: $before, orderBy: $order, filter: {snapshotId: $snapshotId}) {\n edges {\n node {\n id\n snapshotId\n name\n category\n treatment\n owner {\n id\n fullName\n }\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 inherentRiskScore\n residualRiskScore\n note\n owner {\n id\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "95c68a022cbf075eede9b7417393a369"; (node as any).hash = "d8048ddb0ff8d4b08b7b9676d9b98f1a";
export default node; export default node;

View File

@@ -41,6 +41,7 @@ type Props = {
risk?: RiskKey; risk?: RiskKey;
connection?: string; connection?: string;
ref?: ReturnType<typeof useDialogRef>; ref?: ReturnType<typeof useDialogRef>;
onSuccess?: () => void;
}; };
type RiskTemplate = { type RiskTemplate = {
@@ -82,6 +83,7 @@ export default function FormRiskDialog({
risk, risk,
connection, connection,
ref: refProps, ref: refProps,
onSuccess,
}: Props) { }: Props) {
const { __ } = useTranslate(); const { __ } = useTranslate();
const organizationId = useOrganizationId(); const organizationId = useOrganizationId();
@@ -131,6 +133,7 @@ export default function FormRiskDialog({
onSuccess: () => { onSuccess: () => {
ref?.current?.close(); ref?.current?.close();
reset(); reset();
onSuccess?.();
}, },
}); });
}); });

View File

@@ -40,7 +40,17 @@ export default function RisksPage(props: Props) {
const { snapshotId } = useParams<{ snapshotId?: string }>(); const { snapshotId } = useParams<{ snapshotId?: string }>();
const isSnapshotMode = Boolean(snapshotId); const isSnapshotMode = Boolean(snapshotId);
const { connectionId, risks, refetch } = useRisksQuery(props.queryRef, snapshotId); const { connectionId, risks, ...pagination } = useRisksQuery(props.queryRef);
const refetch = ({ order }: { order: { direction: string; field: string } }) => {
pagination.refetch({
snapshotId,
order: {
direction: order.direction as "ASC" | "DESC",
field: order.field as "NAME" | "CATEGORY" | "TREATMENT" | "INHERENT_RISK_SCORE" | "RESIDUAL_RISK_SCORE" | "OWNER_FULL_NAME" | "CREATED_AT"
}
}, { fetchPolicy: 'network-only' });
};
usePageTitle(__("Risks")); usePageTitle(__("Risks"));
@@ -56,6 +66,9 @@ export default function RisksPage(props: Props) {
{!isSnapshotMode && ( {!isSnapshotMode && (
<FormRiskDialog <FormRiskDialog
connection={connectionId} connection={connectionId}
onSuccess={() => {
pagination.refetch({ snapshotId });
}}
trigger={<Button icon={IconPlusLarge}>{__("New Risk")}</Button>} trigger={<Button icon={IconPlusLarge}>{__("New Risk")}</Button>}
/> />
)} )}
@@ -73,7 +86,7 @@ export default function RisksPage(props: Props) {
risks={risks} risks={risks}
/> />
</div> </div>
<SortableTable refetch={refetch}> <SortableTable {...pagination} refetch={refetch}>
<Thead> <Thead>
<Tr> <Tr>
<SortableTh field="NAME">{__("Risk name")}</SortableTh> <SortableTh field="NAME">{__("Risk name")}</SortableTh>
@@ -85,6 +98,9 @@ export default function RisksPage(props: Props) {
<SortableTh field="RESIDUAL_RISK_SCORE"> <SortableTh field="RESIDUAL_RISK_SCORE">
{__("Residual Risk")} {__("Residual Risk")}
</SortableTh> </SortableTh>
<SortableTh field="OWNER_FULL_NAME">
{__("Owner")}
</SortableTh>
<Th></Th> <Th></Th>
</Tr> </Tr>
</Thead> </Thead>
@@ -163,6 +179,7 @@ function RiskRow(props: RowProps) {
<Td> <Td>
<SeverityBadge score={risk.residualRiskScore} /> <SeverityBadge score={risk.residualRiskScore} />
</Td> </Td>
<Td>{risk.owner?.fullName || __("Unassigned")}</Td>
<Td noLink className="text-end"> <Td noLink className="text-end">
{!isSnapshotMode && ( {!isSnapshotMode && (
<ActionDropdown> <ActionDropdown>

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<7d5730a1c721f5256e6e0c07199aa0d2>> * @generated SignedSource<<66f4e8603a811d1fb363ae88ea697b22>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -209,6 +209,20 @@ return {
"name": "residualImpact", "name": "residualImpact",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "inherentRiskScore",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualRiskScore",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -256,12 +270,12 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "e2131409b62d6cd6bd8a6d008f2ac1f5", "cacheID": "a6ca4767b2443f6b4d02d6fdd1358600",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "FormRiskDialogMutation", "name": "FormRiskDialogMutation",
"operationKind": "mutation", "operationKind": "mutation",
"text": "mutation FormRiskDialogMutation(\n $input: CreateRiskInput!\n) {\n createRisk(input: $input) {\n riskEdge {\n node {\n ...useRiskFormFragment\n id\n }\n }\n }\n}\n\nfragment useRiskFormFragment on Risk {\n id\n name\n category\n description\n treatment\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n note\n owner {\n id\n }\n}\n" "text": "mutation FormRiskDialogMutation(\n $input: CreateRiskInput!\n) {\n createRisk(input: $input) {\n riskEdge {\n node {\n ...useRiskFormFragment\n id\n }\n }\n }\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 inherentRiskScore\n residualRiskScore\n note\n owner {\n id\n }\n}\n"
} }
}; };
})(); })();

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<dc5427f9197a47414ed10738473fdca1>> * @generated SignedSource<<4122b3e15107bc0f2c5f8b842b882603>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -178,6 +178,20 @@ return {
"name": "residualImpact", "name": "residualImpact",
"storageKey": null "storageKey": null
}, },
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "inherentRiskScore",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "residualRiskScore",
"storageKey": null
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -206,12 +220,12 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "546b1e60a2e87bf71a1851ca76485767", "cacheID": "59f586a59973ca023df027fdfb0d4bed",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "FormRiskDialogUpdateRiskMutation", "name": "FormRiskDialogUpdateRiskMutation",
"operationKind": "mutation", "operationKind": "mutation",
"text": "mutation FormRiskDialogUpdateRiskMutation(\n $input: UpdateRiskInput!\n) {\n updateRisk(input: $input) {\n risk {\n ...useRiskFormFragment\n id\n }\n }\n}\n\nfragment useRiskFormFragment on Risk {\n id\n name\n category\n description\n treatment\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n note\n owner {\n id\n }\n}\n" "text": "mutation FormRiskDialogUpdateRiskMutation(\n $input: UpdateRiskInput!\n) {\n updateRisk(input: $input) {\n risk {\n ...useRiskFormFragment\n id\n }\n }\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 inherentRiskScore\n residualRiskScore\n note\n owner {\n id\n }\n}\n"
} }
}; };
})(); })();

View File

@@ -46,6 +46,9 @@ type (
SourceID *gid.GID `db:"source_id"` SourceID *gid.GID `db:"source_id"`
CreatedAt time.Time `db:"created_at"` CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"` UpdatedAt time.Time `db:"updated_at"`
// Ordering only
OwnerFullName *string `db:"owner_full_name"`
} }
Risks []*Risk Risks []*Risk
@@ -69,6 +72,8 @@ func (r *Risk) CursorKey(orderBy RiskOrderField) page.CursorKey {
return page.CursorKey{ID: r.ID, Value: r.InherentRiskScore} return page.CursorKey{ID: r.ID, Value: r.InherentRiskScore}
case RiskOrderFieldResidualRiskScore: case RiskOrderFieldResidualRiskScore:
return page.CursorKey{ID: r.ID, Value: r.ResidualRiskScore} return page.CursorKey{ID: r.ID, Value: r.ResidualRiskScore}
case RiskOrderFieldOwnerFullName:
return page.CursorKey{ID: r.ID, Value: r.OwnerFullName}
} }
panic(fmt.Sprintf("unsupported order by: %s", orderBy)) panic(fmt.Sprintf("unsupported order by: %s", orderBy))
@@ -136,6 +141,7 @@ WITH rsks AS (
r.description, r.description,
r.category, r.category,
r.owner_id, r.owner_id,
p.full_name as owner_full_name,
r.treatment, r.treatment,
r.note, r.note,
r.inherent_likelihood, r.inherent_likelihood,
@@ -153,6 +159,8 @@ WITH rsks AS (
risks r risks r
INNER JOIN INNER JOIN
risks_measures rm ON r.id = rm.risk_id risks_measures rm ON r.id = rm.risk_id
LEFT JOIN
peoples p ON r.owner_id = p.id
WHERE WHERE
rm.measure_id = @measure_id rm.measure_id = @measure_id
) )
@@ -163,6 +171,7 @@ SELECT
description, description,
category, category,
owner_id, owner_id,
owner_full_name,
treatment, treatment,
note, note,
inherent_likelihood, inherent_likelihood,
@@ -243,12 +252,43 @@ func (r *Risks) LoadByOrganizationID(
filter *RiskFilter, filter *RiskFilter,
) error { ) error {
q := ` q := `
WITH rsks AS (
SELECT
r.id,
r.tenant_id,
r.organization_id,
r.name,
r.description,
r.owner_id,
p.full_name as owner_full_name,
r.treatment,
r.note,
r.inherent_likelihood,
r.inherent_impact,
r.inherent_risk_score,
r.residual_likelihood,
r.residual_impact,
r.residual_risk_score,
r.category,
r.snapshot_id,
r.source_id,
r.search_vector,
r.created_at,
r.updated_at
FROM
risks r
LEFT JOIN
peoples p ON r.owner_id = p.id
WHERE
r.organization_id = @organization_id
)
SELECT SELECT
id, id,
organization_id, organization_id,
name, name,
description, description,
owner_id, owner_id,
owner_full_name,
treatment, treatment,
note, note,
inherent_likelihood, inherent_likelihood,
@@ -262,9 +302,9 @@ SELECT
source_id, source_id,
created_at, created_at,
updated_at updated_at
FROM risks FROM
rsks
WHERE %s WHERE %s
AND organization_id = @organization_id
AND %s AND %s
AND %s AND %s
` `
@@ -304,6 +344,7 @@ SELECT
description, description,
category, category,
owner_id, owner_id,
NULL as owner_full_name,
treatment, treatment,
note, note,
inherent_likelihood, inherent_likelihood,

View File

@@ -26,6 +26,7 @@ const (
RiskOrderFieldTreatment RiskOrderField = "TREATMENT" RiskOrderFieldTreatment RiskOrderField = "TREATMENT"
RiskOrderFieldInherentRiskScore RiskOrderField = "INHERENT_RISK_SCORE" RiskOrderFieldInherentRiskScore RiskOrderField = "INHERENT_RISK_SCORE"
RiskOrderFieldResidualRiskScore RiskOrderField = "RESIDUAL_RISK_SCORE" RiskOrderFieldResidualRiskScore RiskOrderField = "RESIDUAL_RISK_SCORE"
RiskOrderFieldOwnerFullName RiskOrderField = "OWNER_FULL_NAME"
) )
func (p RiskOrderField) Column() string { func (p RiskOrderField) Column() string {

View File

@@ -431,6 +431,10 @@ enum RiskOrderField
@goEnum( @goEnum(
value: "github.com/getprobo/probo/pkg/coredata.RiskOrderFieldResidualRiskScore" value: "github.com/getprobo/probo/pkg/coredata.RiskOrderFieldResidualRiskScore"
) )
OWNER_FULL_NAME
@goEnum(
value: "github.com/getprobo/probo/pkg/coredata.RiskOrderFieldOwnerFullName"
)
} }
enum EvidenceOrderField enum EvidenceOrderField

View File

@@ -8564,6 +8564,10 @@ enum RiskOrderField
@goEnum( @goEnum(
value: "github.com/getprobo/probo/pkg/coredata.RiskOrderFieldResidualRiskScore" value: "github.com/getprobo/probo/pkg/coredata.RiskOrderFieldResidualRiskScore"
) )
OWNER_FULL_NAME
@goEnum(
value: "github.com/getprobo/probo/pkg/coredata.RiskOrderFieldOwnerFullName"
)
} }
enum EvidenceOrderField enum EvidenceOrderField
@@ -88188,6 +88192,7 @@ var (
"TREATMENT": coredata.RiskOrderFieldTreatment, "TREATMENT": coredata.RiskOrderFieldTreatment,
"INHERENT_RISK_SCORE": coredata.RiskOrderFieldInherentRiskScore, "INHERENT_RISK_SCORE": coredata.RiskOrderFieldInherentRiskScore,
"RESIDUAL_RISK_SCORE": coredata.RiskOrderFieldResidualRiskScore, "RESIDUAL_RISK_SCORE": coredata.RiskOrderFieldResidualRiskScore,
"OWNER_FULL_NAME": coredata.RiskOrderFieldOwnerFullName,
} }
marshalNRiskOrderField2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐRiskOrderField = map[coredata.RiskOrderField]string{ marshalNRiskOrderField2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐRiskOrderField = map[coredata.RiskOrderField]string{
coredata.RiskOrderFieldCreatedAt: "CREATED_AT", coredata.RiskOrderFieldCreatedAt: "CREATED_AT",
@@ -88197,6 +88202,7 @@ var (
coredata.RiskOrderFieldTreatment: "TREATMENT", coredata.RiskOrderFieldTreatment: "TREATMENT",
coredata.RiskOrderFieldInherentRiskScore: "INHERENT_RISK_SCORE", coredata.RiskOrderFieldInherentRiskScore: "INHERENT_RISK_SCORE",
coredata.RiskOrderFieldResidualRiskScore: "RESIDUAL_RISK_SCORE", coredata.RiskOrderFieldResidualRiskScore: "RESIDUAL_RISK_SCORE",
coredata.RiskOrderFieldOwnerFullName: "OWNER_FULL_NAME",
} }
) )