diff --git a/apps/console2/src/components/form/__generated__/UserSelectQuery.graphql.ts b/apps/console2/src/components/form/__generated__/UserSelectQuery.graphql.ts deleted file mode 100644 index ccd28b58f..000000000 --- a/apps/console2/src/components/form/__generated__/UserSelectQuery.graphql.ts +++ /dev/null @@ -1,180 +0,0 @@ -/** - * @generated SignedSource<<45329c77cfacf0f653adac2e9172b371>> - * @lightSyntaxTransform - * @nogrep - */ - -/* tslint:disable */ -/* eslint-disable */ -// @ts-nocheck - -import { ConcreteRequest } from 'relay-runtime'; -export type UserSelectQuery$variables = { - organizationId: string; -}; -export type UserSelectQuery$data = { - readonly organization: { - readonly users?: { - readonly edges: ReadonlyArray<{ - readonly node: { - readonly fullName: string; - readonly id: string; - }; - }>; - }; - }; -}; -export type UserSelectQuery = { - response: UserSelectQuery$data; - variables: UserSelectQuery$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 = { - "kind": "InlineFragment", - "selections": [ - { - "alias": null, - "args": [ - { - "kind": "Literal", - "name": "first", - "value": 100 - }, - { - "kind": "Literal", - "name": "orderBy", - "value": { - "direction": "ASC", - "field": "CREATED_AT" - } - } - ], - "concreteType": "UserConnection", - "kind": "LinkedField", - "name": "users", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "UserEdge", - "kind": "LinkedField", - "name": "edges", - "plural": true, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "User", - "kind": "LinkedField", - "name": "node", - "plural": false, - "selections": [ - (v2/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "fullName", - "storageKey": null - } - ], - "storageKey": null - } - ], - "storageKey": null - } - ], - "storageKey": "users(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"CREATED_AT\"})" - } - ], - "type": "Organization", - "abstractKey": null -}; -return { - "fragment": { - "argumentDefinitions": (v0/*: any*/), - "kind": "Fragment", - "metadata": null, - "name": "UserSelectQuery", - "selections": [ - { - "alias": "organization", - "args": (v1/*: any*/), - "concreteType": null, - "kind": "LinkedField", - "name": "node", - "plural": false, - "selections": [ - (v3/*: any*/) - ], - "storageKey": null - } - ], - "type": "Query", - "abstractKey": null - }, - "kind": "Request", - "operation": { - "argumentDefinitions": (v0/*: any*/), - "kind": "Operation", - "name": "UserSelectQuery", - "selections": [ - { - "alias": "organization", - "args": (v1/*: any*/), - "concreteType": null, - "kind": "LinkedField", - "name": "node", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "__typename", - "storageKey": null - }, - (v3/*: any*/), - (v2/*: any*/) - ], - "storageKey": null - } - ] - }, - "params": { - "cacheID": "795bb4c1131f2cacedb1ab8faf9f10ed", - "id": null, - "metadata": {}, - "name": "UserSelectQuery", - "operationKind": "query", - "text": "query UserSelectQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n users(first: 100, orderBy: {direction: ASC, field: CREATED_AT}) {\n edges {\n node {\n id\n fullName\n }\n }\n }\n }\n id\n }\n}\n" - } -}; -})(); - -(node as any).hash = "f2a6754264e15c49c53a9ac9fa60264e"; - -export default node; diff --git a/apps/console2/src/components/skeletons/PageSkeleton.tsx b/apps/console2/src/components/skeletons/PageSkeleton.tsx new file mode 100644 index 000000000..05cbaf28b --- /dev/null +++ b/apps/console2/src/components/skeletons/PageSkeleton.tsx @@ -0,0 +1,12 @@ +import { PageHeader, Skeleton } from "@probo/ui"; + +export function PageSkeleton() { + return ( +
+ } /> +
+ +
+
+ ); +} diff --git a/apps/console2/src/pages/organizations/risks/skeletons/RisksPage.tsx b/apps/console2/src/components/skeletons/RisksPageSkeleton.tsx similarity index 100% rename from apps/console2/src/pages/organizations/risks/skeletons/RisksPage.tsx rename to apps/console2/src/components/skeletons/RisksPageSkeleton.tsx diff --git a/apps/console2/src/hooks/useMutationWithToasts.ts b/apps/console2/src/hooks/useMutationWithToasts.ts index 3a5325612..71a3d2f2f 100644 --- a/apps/console2/src/hooks/useMutationWithToasts.ts +++ b/apps/console2/src/hooks/useMutationWithToasts.ts @@ -8,19 +8,24 @@ import type { MutationParameters, GraphQLTaggedNode } from "relay-runtime"; * A decorated useMutation hook that emits toast notifications on success or error. */ export function useMutationWithToasts( - query: GraphQLTaggedNode + query: GraphQLTaggedNode, + baseOptions?: { + successMessage?: string; + errorMessage?: string; + } ) { const [mutate, isLoading] = useMutation(query); const { toast } = useToast(); const { __ } = useTranslate(); const mutateWithToast = useCallback( ( - options: UseMutationConfig & { + queryOptions: UseMutationConfig & { onSuccess?: () => void; - successMessage: string; + successMessage?: string; errorMessage?: string; } ) => { + const options = { ...baseOptions, ...queryOptions }; mutate({ ...options, onCompleted: (_, error) => { @@ -35,7 +40,8 @@ export function useMutationWithToasts( } toast({ title: __("Success"), - description: options.successMessage, + description: + options.successMessage ?? __("Operation completed successfully"), variant: "success", }); options.onSuccess?.(); diff --git a/apps/console2/src/index.css b/apps/console2/src/index.css index bf6b63585..dc3c6d32d 100644 --- a/apps/console2/src/index.css +++ b/apps/console2/src/index.css @@ -3,3 +3,4 @@ @import "@probo/ui/src/theme.css"; @import "tw-animate-css"; @source "../../../packages/ui/src"; +@source "../../../packages/helpers/src"; diff --git a/apps/console2/src/mutations/Risks.ts b/apps/console2/src/mutations/Risks.ts new file mode 100644 index 000000000..2f4fe511b --- /dev/null +++ b/apps/console2/src/mutations/Risks.ts @@ -0,0 +1,21 @@ +import { graphql } from "relay-runtime"; +import { useTranslate } from "@probo/i18n"; +import { useMutationWithToasts } from "../hooks/useMutationWithToasts"; +import type { RisksDeleteMutation } from "./__generated__/RisksDeleteMutation.graphql"; + +const deleteRiskMutation = graphql` + mutation RisksDeleteMutation($input: DeleteRiskInput!, $connections: [ID!]!) { + deleteRisk(input: $input) { + deletedRiskId @deleteEdge(connections: $connections) + } + } +`; + +export function useDeleteRiskMutation() { + const { __ } = useTranslate(); + + return useMutationWithToasts(deleteRiskMutation, { + successMessage: __("Risk deleted successfully."), + errorMessage: __("Failed to delete risk. Please try again."), + }); +} diff --git a/apps/console2/src/mutations/__generated__/RisksDeleteMutation.graphql.ts b/apps/console2/src/mutations/__generated__/RisksDeleteMutation.graphql.ts new file mode 100644 index 000000000..b0047a5cc --- /dev/null +++ b/apps/console2/src/mutations/__generated__/RisksDeleteMutation.graphql.ts @@ -0,0 +1,132 @@ +/** + * @generated SignedSource<<5102c0f9ea660855dc5db7c6b16fd64b>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type DeleteRiskInput = { + riskId: string; +}; +export type RisksDeleteMutation$variables = { + connections: ReadonlyArray; + input: DeleteRiskInput; +}; +export type RisksDeleteMutation$data = { + readonly deleteRisk: { + readonly deletedRiskId: string; + }; +}; +export type RisksDeleteMutation = { + response: RisksDeleteMutation$data; + variables: RisksDeleteMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "connections" +}, +v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" +}, +v2 = [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } +], +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "deletedRiskId", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "RisksDeleteMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "DeleteRiskPayload", + "kind": "LinkedField", + "name": "deleteRisk", + "plural": false, + "selections": [ + (v3/*: any*/) + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "RisksDeleteMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "DeleteRiskPayload", + "kind": "LinkedField", + "name": "deleteRisk", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "filters": null, + "handle": "deleteEdge", + "key": "", + "kind": "ScalarHandle", + "name": "deletedRiskId", + "handleArgs": [ + { + "kind": "Variable", + "name": "connections", + "variableName": "connections" + } + ] + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "f4273ec8d2cd76543169a6d1b7cd72b3", + "id": null, + "metadata": {}, + "name": "RisksDeleteMutation", + "operationKind": "mutation", + "text": "mutation RisksDeleteMutation(\n $input: DeleteRiskInput!\n) {\n deleteRisk(input: $input) {\n deletedRiskId\n }\n}\n" + } +}; +})(); + +(node as any).hash = "ad609ad0f9c749fe47f2bd0d382777bb"; + +export default node; diff --git a/apps/console2/src/pages/__generated__/OrganizationSelectionPageQuery.graphql.ts b/apps/console2/src/pages/__generated__/OrganizationSelectionPageQuery.graphql.ts deleted file mode 100644 index d67cd011f..000000000 --- a/apps/console2/src/pages/__generated__/OrganizationSelectionPageQuery.graphql.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * @generated SignedSource<<282a94b76fe5e17bd40b45ced6445ef3>> - * @lightSyntaxTransform - * @nogrep - */ - -/* tslint:disable */ -/* eslint-disable */ -// @ts-nocheck - -import { ConcreteRequest } from 'relay-runtime'; -export type OrganizationSelectionPageQuery$variables = Record; -export type OrganizationSelectionPageQuery$data = { - readonly viewer: { - readonly organizations: { - readonly edges: ReadonlyArray<{ - readonly node: { - readonly id: string; - readonly logoUrl: string | null | undefined; - readonly name: string; - }; - }>; - }; - }; -}; -export type OrganizationSelectionPageQuery = { - response: OrganizationSelectionPageQuery$data; - variables: OrganizationSelectionPageQuery$variables; -}; - -const node: ConcreteRequest = (function(){ -var v0 = { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "id", - "storageKey": null -}, -v1 = { - "alias": null, - "args": [ - { - "kind": "Literal", - "name": "first", - "value": 25 - } - ], - "concreteType": "OrganizationConnection", - "kind": "LinkedField", - "name": "organizations", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "OrganizationEdge", - "kind": "LinkedField", - "name": "edges", - "plural": true, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "Organization", - "kind": "LinkedField", - "name": "node", - "plural": false, - "selections": [ - (v0/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "name", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "logoUrl", - "storageKey": null - } - ], - "storageKey": null - } - ], - "storageKey": null - } - ], - "storageKey": "organizations(first:25)" -}; -return { - "fragment": { - "argumentDefinitions": [], - "kind": "Fragment", - "metadata": null, - "name": "OrganizationSelectionPageQuery", - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "Viewer", - "kind": "LinkedField", - "name": "viewer", - "plural": false, - "selections": [ - (v1/*: any*/) - ], - "storageKey": null - } - ], - "type": "Query", - "abstractKey": null - }, - "kind": "Request", - "operation": { - "argumentDefinitions": [], - "kind": "Operation", - "name": "OrganizationSelectionPageQuery", - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "Viewer", - "kind": "LinkedField", - "name": "viewer", - "plural": false, - "selections": [ - (v1/*: any*/), - (v0/*: any*/) - ], - "storageKey": null - } - ] - }, - "params": { - "cacheID": "2938cb693a84c22366862de424a84d4b", - "id": null, - "metadata": {}, - "name": "OrganizationSelectionPageQuery", - "operationKind": "query", - "text": "query OrganizationSelectionPageQuery {\n viewer {\n organizations(first: 25) {\n edges {\n node {\n id\n name\n logoUrl\n }\n }\n }\n id\n }\n}\n" - } -}; -})(); - -(node as any).hash = "c41774fd198ab5f0f4b866d805099baf"; - -export default node; diff --git a/apps/console2/src/pages/organizations/risks/FormRiskDialog.tsx b/apps/console2/src/pages/organizations/risks/FormRiskDialog.tsx index 88ee01afb..bf38d3205 100644 --- a/apps/console2/src/pages/organizations/risks/FormRiskDialog.tsx +++ b/apps/console2/src/pages/organizations/risks/FormRiskDialog.tsx @@ -72,7 +72,12 @@ const updateRiskMutation = graphql` /** * Dialog to create or update a risk */ -export default function FormRiskDialog({ trigger, risk, onSuccess }: Props) { +export default function FormRiskDialog({ + trigger, + risk, + onSuccess, + open, +}: Props) { const { __ } = useTranslate(); const organizationId = useOrganizationId(); @@ -131,11 +136,11 @@ export default function FormRiskDialog({ trigger, risk, onSuccess }: Props) { }); const [showNote, toggleNote] = useToggle(false); - const [open, setOpen] = useState(!!risk); + const [isOpen, setOpen] = useState(open); return ( (); + const organizationId = useOrganizationId(); + const navigate = useNavigate(); + + if (!riskId) { + throw new Error("Cannot load risk detail page without riskId parameter"); + } + + const { __ } = useTranslate(); + const data = useLazyLoadQuery(riskQuery, { riskId }); + const risk = data.node as Required; + const [deleteRisk] = useDeleteRiskMutation(); + + usePageTitle(risk.name ?? "Risk detail"); + + const onDelete = (riskId: string) => { + const connectionId = ConnectionHandler.getConnectionID( + organizationId, + "RisksPage_risks" + ); + deleteRisk({ + variables: { + input: { riskId }, + connections: [connectionId], + }, + onSuccess() { + navigate(`/organizations/${organizationId}/risks`); + }, + }); + }; + + return ( +
+ {/* Header */} +
+ +
+ + {__("Edit")} + + } + risk={{ id: riskId, ...risk }} + /> + + onDelete(riskId)} + > + + {__("Delete")} + + + +
+
+ + + +
+ + +
+ + + + + + {risk.owner?.fullName} + + + + + {getTreatment(__, risk.treatment)} + + + +
{risk.note}
+
+
+
+ ); +} diff --git a/apps/console2/src/pages/organizations/risks/RisksPage.tsx b/apps/console2/src/pages/organizations/risks/RisksPage.tsx index fe7d1f777..7b9f4f8e5 100644 --- a/apps/console2/src/pages/organizations/risks/RisksPage.tsx +++ b/apps/console2/src/pages/organizations/risks/RisksPage.tsx @@ -30,8 +30,9 @@ import { useMemo, useState } from "react"; import { usePageTitle } from "@probo/hooks"; import type { RisksPageDeleteMutation } from "./__generated__/RisksPageDeleteMutation.graphql"; import { useMutationWithToasts } from "../../../hooks/useMutationWithToasts"; -import { sprintf } from "@probo/helpers"; +import { getTreatment, sprintf } from "@probo/helpers"; import type { ItemOf } from "../../../types"; +import { useDeleteRiskMutation } from "../../../mutations/Risks"; const risksQuery = graphql` query RisksPageQuery( @@ -65,17 +66,6 @@ const risksQuery = graphql` } `; -const deleteRiskMutation = graphql` - mutation RisksPageDeleteMutation( - $input: DeleteRiskInput! - $connections: [ID!]! - ) { - deleteRisk(input: $input) { - deletedRiskId @deleteEdge(connections: $connections) - } - } -`; - export default function RisksPage() { const { __ } = useTranslate(); const data = useLazyLoadQuery(risksQuery, { @@ -85,18 +75,9 @@ export default function RisksPage() { const [editedRisk, setEditedRisk] = useState | null>( null ); - const treatmentLabels = useMemo(() => { - return { - MITIGATED: __("Mitigate"), - ACCEPTED: __("Accept"), - TRANSFERRED: __("Transfer"), - AVOIDED: __("Avoid"), - } as Record; - }, [__]); const organizationId = useOrganizationId(); - const [deleteRisk] = - useMutationWithToasts(deleteRiskMutation); + const [deleteRisk] = useDeleteRiskMutation(); const onDelete = (riskId: string) => { const connectionId = ConnectionHandler.getConnectionID( @@ -108,8 +89,6 @@ export default function RisksPage() { input: { riskId }, connections: [connectionId], }, - successMessage: __("Risk deleted successfully."), - errorMessage: __("Failed to delete risk. Please try again."), }); }; @@ -124,6 +103,7 @@ export default function RisksPage() { {editedRisk && ( setEditedRisk(null)} /> @@ -161,7 +141,7 @@ export default function RisksPage() { > {risk.name} {risk.category} - {treatmentLabels[risk.treatment]} + {getTreatment(__, risk.treatment)} > - * @lightSyntaxTransform - * @nogrep - */ - -/* tslint:disable */ -/* eslint-disable */ -// @ts-nocheck - -import { ConcreteRequest } from 'relay-runtime'; -export type RiskTreatment = "ACCEPTED" | "AVOIDED" | "MITIGATED" | "TRANSFERRED" | "%future added value"; -export type CreateRiskInput = { - category: string; - description: string; - inherentImpact: number; - inherentLikelihood: number; - name: string; - note?: string | null | undefined; - organizationId: string; - ownerId?: string | null | undefined; - residualImpact?: number | null | undefined; - residualLikelihood?: number | null | undefined; - treatment: RiskTreatment; -}; -export type NewRiskDialogMutation$variables = { - connections: ReadonlyArray; - input: CreateRiskInput; -}; -export type NewRiskDialogMutation$data = { - readonly createRisk: { - readonly riskEdge: { - readonly node: { - readonly category: string; - readonly createdAt: any; - readonly description: string; - readonly id: string; - readonly inherentImpact: number; - readonly inherentLikelihood: number; - readonly name: string; - readonly residualImpact: number; - readonly residualLikelihood: number; - readonly treatment: RiskTreatment; - readonly updatedAt: any; - }; - }; - }; -}; -export type NewRiskDialogMutation = { - response: NewRiskDialogMutation$data; - variables: NewRiskDialogMutation$variables; -}; - -const node: ConcreteRequest = (function(){ -var v0 = { - "defaultValue": null, - "kind": "LocalArgument", - "name": "connections" -}, -v1 = { - "defaultValue": null, - "kind": "LocalArgument", - "name": "input" -}, -v2 = [ - { - "kind": "Variable", - "name": "input", - "variableName": "input" - } -], -v3 = { - "alias": null, - "args": null, - "concreteType": "RiskEdge", - "kind": "LinkedField", - "name": "riskEdge", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "Risk", - "kind": "LinkedField", - "name": "node", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "id", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "name", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "description", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "category", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "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": "treatment", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "createdAt", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "updatedAt", - "storageKey": null - } - ], - "storageKey": null - } - ], - "storageKey": null -}; -return { - "fragment": { - "argumentDefinitions": [ - (v0/*: any*/), - (v1/*: any*/) - ], - "kind": "Fragment", - "metadata": null, - "name": "NewRiskDialogMutation", - "selections": [ - { - "alias": null, - "args": (v2/*: any*/), - "concreteType": "CreateRiskPayload", - "kind": "LinkedField", - "name": "createRisk", - "plural": false, - "selections": [ - (v3/*: any*/) - ], - "storageKey": null - } - ], - "type": "Mutation", - "abstractKey": null - }, - "kind": "Request", - "operation": { - "argumentDefinitions": [ - (v1/*: any*/), - (v0/*: any*/) - ], - "kind": "Operation", - "name": "NewRiskDialogMutation", - "selections": [ - { - "alias": null, - "args": (v2/*: any*/), - "concreteType": "CreateRiskPayload", - "kind": "LinkedField", - "name": "createRisk", - "plural": false, - "selections": [ - (v3/*: any*/), - { - "alias": null, - "args": null, - "filters": null, - "handle": "prependEdge", - "key": "", - "kind": "LinkedHandle", - "name": "riskEdge", - "handleArgs": [ - { - "kind": "Variable", - "name": "connections", - "variableName": "connections" - } - ] - } - ], - "storageKey": null - } - ] - }, - "params": { - "cacheID": "e9b461ea2a98edd7d4a1b289d5d4673f", - "id": null, - "metadata": {}, - "name": "NewRiskDialogMutation", - "operationKind": "mutation", - "text": "mutation NewRiskDialogMutation(\n $input: CreateRiskInput!\n) {\n createRisk(input: $input) {\n riskEdge {\n node {\n id\n name\n description\n category\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n treatment\n createdAt\n updatedAt\n }\n }\n }\n}\n" - } -}; -})(); - -(node as any).hash = "532223f85627f715a9a457680073f358"; - -export default node; diff --git a/apps/console2/src/pages/organizations/risks/__generated__/RiskDetailPageQuery.graphql.ts b/apps/console2/src/pages/organizations/risks/__generated__/RiskDetailPageQuery.graphql.ts new file mode 100644 index 000000000..554016736 --- /dev/null +++ b/apps/console2/src/pages/organizations/risks/__generated__/RiskDetailPageQuery.graphql.ts @@ -0,0 +1,262 @@ +/** + * @generated SignedSource<> + * @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 RiskDetailPageQuery$variables = { + riskId: string; +}; +export type RiskDetailPageQuery$data = { + readonly node: { + readonly createdAt?: any; + readonly description?: string; + readonly inherentImpact?: number; + readonly inherentLikelihood?: number; + readonly name?: string; + readonly note?: string; + readonly owner?: { + readonly fullName: string; + readonly id: string; + } | null | undefined; + readonly residualImpact?: number; + readonly residualLikelihood?: number; + readonly treatment?: RiskTreatment; + readonly updatedAt?: any; + readonly " $fragmentSpreads": FragmentRefs<"useRiskFormFragment">; + }; +}; +export type RiskDetailPageQuery = { + response: RiskDetailPageQuery$data; + variables: RiskDetailPageQuery$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": "inherentLikelihood", + "storageKey": null +}, +v8 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "inherentImpact", + "storageKey": null +}, +v9 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "residualLikelihood", + "storageKey": null +}, +v10 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "residualImpact", + "storageKey": null +}, +v11 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "note", + "storageKey": null +}, +v12 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null +}, +v13 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "updatedAt", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "RiskDetailPageQuery", + "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*/), + (v8/*: any*/), + (v9/*: any*/), + (v10/*: any*/), + (v11/*: any*/), + (v12/*: any*/), + (v13/*: any*/), + { + "args": null, + "kind": "FragmentSpread", + "name": "useRiskFormFragment" + } + ], + "type": "Risk", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "RiskDetailPageQuery", + "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*/), + (v8/*: any*/), + (v9/*: any*/), + (v10/*: any*/), + (v11/*: any*/), + (v12/*: any*/), + (v13/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "category", + "storageKey": null + } + ], + "type": "Risk", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "0ed24be13c829da91705d506a46eb562", + "id": null, + "metadata": {}, + "name": "RiskDetailPageQuery", + "operationKind": "query", + "text": "query RiskDetailPageQuery(\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 inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n note\n createdAt\n updatedAt\n ...useRiskFormFragment\n }\n id\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" + } +}; +})(); + +(node as any).hash = "94de027c557f9b633765f9dde7c0aae1"; + +export default node; diff --git a/apps/console2/src/pages/organizations/risks/__generated__/RisksPageUpdateRiskMutation.graphql.ts b/apps/console2/src/pages/organizations/risks/__generated__/RisksPageUpdateRiskMutation.graphql.ts deleted file mode 100644 index af1bd5bcb..000000000 --- a/apps/console2/src/pages/organizations/risks/__generated__/RisksPageUpdateRiskMutation.graphql.ts +++ /dev/null @@ -1,220 +0,0 @@ -/** - * @generated SignedSource<<78e5a6f1db510ffe28be99140539b234>> - * @lightSyntaxTransform - * @nogrep - */ - -/* tslint:disable */ -/* eslint-disable */ -// @ts-nocheck - -import { ConcreteRequest } from 'relay-runtime'; -export type RiskTreatment = "ACCEPTED" | "AVOIDED" | "MITIGATED" | "TRANSFERRED" | "%future added value"; -export type UpdateRiskInput = { - category?: string | null | undefined; - description?: string | null | undefined; - id: string; - inherentImpact?: number | null | undefined; - inherentLikelihood?: number | null | undefined; - name?: string | null | undefined; - note?: string | null | undefined; - ownerId?: string | null | undefined; - residualImpact?: number | null | undefined; - residualLikelihood?: number | null | undefined; - treatment?: RiskTreatment | null | undefined; -}; -export type RisksPageUpdateRiskMutation$variables = { - input: UpdateRiskInput; -}; -export type RisksPageUpdateRiskMutation$data = { - readonly updateRisk: { - readonly risk: { - readonly category: string; - readonly description: string; - readonly id: string; - readonly inherentImpact: number; - readonly inherentLikelihood: number; - readonly name: string; - readonly note: string; - readonly owner: { - readonly fullName: string; - readonly id: string; - } | null | undefined; - readonly residualImpact: number; - readonly residualLikelihood: number; - readonly treatment: RiskTreatment; - readonly updatedAt: any; - }; - }; -}; -export type RisksPageUpdateRiskMutation = { - response: RisksPageUpdateRiskMutation$data; - variables: RisksPageUpdateRiskMutation$variables; -}; - -const node: ConcreteRequest = (function(){ -var v0 = [ - { - "defaultValue": null, - "kind": "LocalArgument", - "name": "input" - } -], -v1 = { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "id", - "storageKey": null -}, -v2 = [ - { - "alias": null, - "args": [ - { - "kind": "Variable", - "name": "input", - "variableName": "input" - } - ], - "concreteType": "UpdateRiskPayload", - "kind": "LinkedField", - "name": "updateRisk", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "Risk", - "kind": "LinkedField", - "name": "risk", - "plural": false, - "selections": [ - (v1/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "name", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "description", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "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": "treatment", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "note", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "updatedAt", - "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 - } - ], - "storageKey": null - } - ], - "storageKey": null - } -]; -return { - "fragment": { - "argumentDefinitions": (v0/*: any*/), - "kind": "Fragment", - "metadata": null, - "name": "RisksPageUpdateRiskMutation", - "selections": (v2/*: any*/), - "type": "Mutation", - "abstractKey": null - }, - "kind": "Request", - "operation": { - "argumentDefinitions": (v0/*: any*/), - "kind": "Operation", - "name": "RisksPageUpdateRiskMutation", - "selections": (v2/*: any*/) - }, - "params": { - "cacheID": "a3c421bf079967f96acf2215e72cf8a7", - "id": null, - "metadata": {}, - "name": "RisksPageUpdateRiskMutation", - "operationKind": "mutation", - "text": "mutation RisksPageUpdateRiskMutation(\n $input: UpdateRiskInput!\n) {\n updateRisk(input: $input) {\n risk {\n id\n name\n description\n category\n inherentLikelihood\n inherentImpact\n residualLikelihood\n residualImpact\n treatment\n note\n updatedAt\n owner {\n id\n fullName\n }\n }\n }\n}\n" - } -}; -})(); - -(node as any).hash = "e3c8daa449e937742695adf8deb6dbe5"; - -export default node; diff --git a/apps/console2/src/routes.tsx b/apps/console2/src/routes.tsx index 4386b94df..c2437e7df 100644 --- a/apps/console2/src/routes.tsx +++ b/apps/console2/src/routes.tsx @@ -7,7 +7,8 @@ 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 { RisksPageSkeleton } from "./pages/organizations/risks/skeletons/RisksPage"; +import { RisksPageSkeleton } from "./components/skeletons/RisksPageSkeleton.tsx"; +import { PageSkeleton } from "./components/skeletons/PageSkeleton.tsx"; function ErrorBoundary() { const error = useRouteError(); @@ -49,7 +50,7 @@ const routes = [ { path: "organizations/new", Component: lazy( - () => import("./pages/organizations/NewOrganizationPage") + () => import("./pages/organizations/NewOrganizationPage"), ), }, ], @@ -64,6 +65,13 @@ const routes = [ fallback: RisksPageSkeleton, Component: lazy(() => import("./pages/organizations/risks/RisksPage")), }, + { + path: "risks/:riskId", + fallback: PageSkeleton, + Component: lazy( + () => import("./pages/organizations/risks/RiskDetailPage"), + ), + }, ], }, ] satisfies Route[]; @@ -74,7 +82,6 @@ const routes = [ function routeTransformer(route: Route): RouteObject { if ("fallback" in route && route.fallback) { const fallback = ; - console.log(fallback, route.path); return { ...route, children: route.children?.map(routeTransformer), diff --git a/packages/helpers/src/index.ts b/packages/helpers/src/index.ts index cb8880e62..aec95e965 100644 --- a/packages/helpers/src/index.ts +++ b/packages/helpers/src/index.ts @@ -1,5 +1,10 @@ export { objectKeys } from "./object"; export { sprintf } from "./string"; -export { getRiskImpacts, getRiskLikelihoods } from "./risk"; +export { + getTreatment, + getRiskImpacts, + getRiskLikelihoods, + getSeverity, +} from "./risk"; export { times, groupBy } from "./array"; export { randomInt } from "./number"; diff --git a/packages/helpers/src/risk.ts b/packages/helpers/src/risk.ts index e44428593..5eec1cc2c 100644 --- a/packages/helpers/src/risk.ts +++ b/packages/helpers/src/risk.ts @@ -25,6 +25,21 @@ export function getRiskImpacts(__: Translator) { ]; } +export function getTreatment(__: Translator, treatment: string): string { + switch (treatment) { + case "MITIGATED": + return __("Mitigate"); + case "ACCEPTED": + return __("Accept"); + case "TRANSFERRED": + return __("Transfer"); + case "AVOIDED": + return __("Avoid"); + default: + return __("Unknown"); + } +} + export function getRiskLikelihoods(__: Translator) { return [ { @@ -49,3 +64,33 @@ export function getRiskLikelihoods(__: Translator) { }, ]; } + +function getRiskSeverities(__: Translator) { + return [ + { + min: 15, + variant: "danger", + label: __("Critical"), + bg: "bg-danger", + color: "text-txt-danger", + }, + { + min: 5, + variant: "warning", + label: __("High"), + bg: "bg-warning", + color: "text-txt-warning", + }, + { + min: 0, + variant: "neutral", + label: __("Low"), + bg: "bg-txt-quaternary", + color: "text-txt-primary", + }, + ] as const; +} + +export function getSeverity(__: Translator, score: number) { + return getRiskSeverities(__).find((s) => score >= s.min); +} diff --git a/packages/ui/.storybook/preview.css b/packages/ui/.storybook/preview.css index f4a6c20d1..a82a2d77f 100644 --- a/packages/ui/.storybook/preview.css +++ b/packages/ui/.storybook/preview.css @@ -2,3 +2,4 @@ @import "tailwindcss"; @import "tw-animate-css"; @import "../src/theme.css"; +@source "../../helpers/src"; diff --git a/packages/ui/src/Atoms/Badge/Badge.stories.tsx b/packages/ui/src/Atoms/Badge/Badge.stories.tsx index 952cf0da5..377e8b74b 100644 --- a/packages/ui/src/Atoms/Badge/Badge.stories.tsx +++ b/packages/ui/src/Atoms/Badge/Badge.stories.tsx @@ -9,23 +9,34 @@ export default { type Story = StoryObj; +const sizes = ["sm", "md"] as const; +const variants = [ + "success", + "warning", + "danger", + "info", + "neutral", + "outline", + "highlight", +] as const; + export const Default: Story = { render: () => ( - <> - {( - [ - "success", - "warning", - "danger", - "info", - "neutral", - "outline", - ] as const - ).map((variant) => ( - - {variant} - +
+ {sizes.map((size) => ( +
+
+ Size : {size} +
+
+ {variants.map((variant) => ( + + {variant} + + ))} +
+
))} - +
), }; diff --git a/packages/ui/src/Atoms/Badge/Badge.tsx b/packages/ui/src/Atoms/Badge/Badge.tsx index fe219b62a..72909fb15 100644 --- a/packages/ui/src/Atoms/Badge/Badge.tsx +++ b/packages/ui/src/Atoms/Badge/Badge.tsx @@ -2,11 +2,19 @@ import type { HTMLAttributes } from "react"; import { tv } from "tailwind-variants"; type Props = { - variant?: "success" | "warning" | "danger" | "info" | "neutral" | "outline"; + variant?: + | "success" + | "warning" + | "danger" + | "info" + | "neutral" + | "outline" + | "highlight"; + size?: "sm" | "md"; } & HTMLAttributes; const badge = tv({ - base: "text-xs font-medium py-[2px] px-[6px] rounded-lg w-max", + base: "font-medium rounded-lg w-max flex gap-1 items-center", variants: { variant: { success: "bg-success text-txt-success", @@ -15,10 +23,18 @@ const badge = tv({ info: "bg-info text-txt-info", neutral: "bg-subtle text-txt-secondary", outline: "text-txt-tertiary border border-border-low", + highlight: "bg-highlight text-txt-primary", }, + size: { + sm: "text-xs py-[2px] px-[6px]", + md: "text-sm py-[6px] px-2", + }, + }, + defaultVariants: { + size: "md", }, }); export function Badge(props: Props) { - return
; + return
; } diff --git a/packages/ui/src/Atoms/Dropdown/Dropdown.tsx b/packages/ui/src/Atoms/Dropdown/Dropdown.tsx index e7a7ca907..802286cc1 100644 --- a/packages/ui/src/Atoms/Dropdown/Dropdown.tsx +++ b/packages/ui/src/Atoms/Dropdown/Dropdown.tsx @@ -61,12 +61,19 @@ const dropdownItem = tv({ }, }); -export function ActionDropdown(props: Omit) { +export function ActionDropdown( + props: Omit & { + variant?: ComponentProps["variant"]; + }, +) { return ( +