Fix measure tasks page permissions handling

Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
Émile Ré
2025-12-31 11:40:43 +01:00
committed by Bryan Frimin
parent 21353f35e7
commit 8d51888895
9 changed files with 294 additions and 140 deletions

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<5dfe8149e11942df0913ea8ba61e7ee5>> * @generated SignedSource<<587b1edc25e82b42dd300f9fe7424bc3>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -15,8 +15,14 @@ export type MeasureGraphListQuery$variables = {
}; };
export type MeasureGraphListQuery$data = { export type MeasureGraphListQuery$data = {
readonly organization: { readonly organization: {
readonly __typename: "Organization";
readonly canCreateMeasure: boolean;
readonly id: string; readonly id: string;
readonly " $fragmentSpreads": FragmentRefs<"MeasuresPageFragment">; readonly " $fragmentSpreads": FragmentRefs<"MeasuresPageFragment">;
} | {
// This will never be '%other', but we need some
// value in case none of the concrete values match.
readonly __typename: "%other";
}; };
}; };
export type MeasureGraphListQuery = { export type MeasureGraphListQuery = {
@@ -43,17 +49,30 @@ v2 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "id", "name": "__typename",
"storageKey": null "storageKey": null
}, },
v3 = { v3 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "__typename", "name": "id",
"storageKey": null "storageKey": null
}, },
v4 = [ v4 = {
"alias": "canCreateMeasure",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:measure:create"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:measure:create\")"
},
v5 = [
{ {
"kind": "Literal", "kind": "Literal",
"name": "first", "name": "first",
@@ -68,21 +87,34 @@ return {
"name": "MeasureGraphListQuery", "name": "MeasureGraphListQuery",
"selections": [ "selections": [
{ {
"alias": "organization", "kind": "RequiredField",
"args": (v1/*: any*/), "field": {
"concreteType": null, "alias": "organization",
"kind": "LinkedField", "args": (v1/*: any*/),
"name": "node", "concreteType": null,
"plural": false, "kind": "LinkedField",
"selections": [ "name": "node",
(v2/*: any*/), "plural": false,
{ "selections": [
"args": null, (v2/*: any*/),
"kind": "FragmentSpread", {
"name": "MeasuresPageFragment" "kind": "InlineFragment",
} "selections": [
], (v3/*: any*/),
"storageKey": null (v4/*: any*/),
{
"args": null,
"kind": "FragmentSpread",
"name": "MeasuresPageFragment"
}
],
"type": "Organization",
"abstractKey": null
}
],
"storageKey": null
},
"action": "THROW"
} }
], ],
"type": "Query", "type": "Query",
@@ -102,14 +134,15 @@ return {
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v3/*: any*/),
(v2/*: any*/), (v2/*: any*/),
(v3/*: any*/),
{ {
"kind": "InlineFragment", "kind": "InlineFragment",
"selections": [ "selections": [
(v4/*: any*/),
{ {
"alias": null, "alias": null,
"args": (v4/*: any*/), "args": (v5/*: any*/),
"concreteType": "MeasureConnection", "concreteType": "MeasureConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "measures", "name": "measures",
@@ -131,7 +164,7 @@ return {
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v2/*: any*/), (v3/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -153,6 +186,32 @@ return {
"name": "state", "name": "state",
"storageKey": null "storageKey": null
}, },
{
"alias": "canUpdate",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:measure:update"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:measure:update\")"
},
{
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:measure:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:measure:delete\")"
},
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -160,7 +219,7 @@ return {
"name": "description", "name": "description",
"storageKey": null "storageKey": null
}, },
(v3/*: any*/) (v2/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
@@ -216,7 +275,7 @@ return {
}, },
{ {
"alias": null, "alias": null,
"args": (v4/*: any*/), "args": (v5/*: any*/),
"filters": null, "filters": null,
"handle": "connection", "handle": "connection",
"key": "MeasuresGraphListQuery__measures", "key": "MeasuresGraphListQuery__measures",
@@ -233,16 +292,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "644632c32801939ffc06c8258b3060f3", "cacheID": "28d1efc46af83ac0b77b4bbf254ead9f",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "MeasureGraphListQuery", "name": "MeasureGraphListQuery",
"operationKind": "query", "operationKind": "query",
"text": "query MeasureGraphListQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n id\n ...MeasuresPageFragment\n }\n}\n\nfragment MeasureFormDialogMeasureFragment on Measure {\n id\n description\n name\n category\n state\n}\n\nfragment MeasuresPageFragment on Organization {\n measures(first: 500) {\n edges {\n node {\n id\n name\n category\n state\n ...MeasureFormDialogMeasureFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n" "text": "query MeasureGraphListQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n canCreateMeasure: permission(action: \"core:measure:create\")\n ...MeasuresPageFragment\n }\n id\n }\n}\n\nfragment MeasureFormDialogMeasureFragment on Measure {\n id\n description\n name\n category\n state\n}\n\nfragment MeasuresPageFragment on Organization {\n measures(first: 500) {\n edges {\n node {\n id\n name\n category\n state\n canUpdate: permission(action: \"core:measure:update\")\n canDelete: permission(action: \"core:measure:delete\")\n ...MeasureFormDialogMeasureFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "3014f5724257c0819ba6ef15d2b72d07"; (node as any).hash = "291184a9b739da290f087db35714ba6b";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<ba6c0f7696cba9d0f9e6e092f6c0f263>> * @generated SignedSource<<68d7a7680b2d8573d9f05e2c33e2e516>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -16,6 +16,9 @@ export type MeasureGraphNodeQuery$variables = {
}; };
export type MeasureGraphNodeQuery$data = { export type MeasureGraphNodeQuery$data = {
readonly node: { readonly node: {
readonly canDelete?: boolean;
readonly canListTasks?: boolean;
readonly canUpdate?: boolean;
readonly category?: string; readonly category?: string;
readonly controlsInfos?: { readonly controlsInfos?: {
readonly totalCount: number; readonly totalCount: number;
@@ -88,14 +91,53 @@ v6 = {
"name": "category", "name": "category",
"storageKey": null "storageKey": null
}, },
v7 = [ v7 = {
"alias": "canUpdate",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:measure:update"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:measure:update\")"
},
v8 = {
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:measure:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:measure:delete\")"
},
v9 = {
"alias": "canListTasks",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:task:list"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:task:list\")"
},
v10 = [
{ {
"kind": "Literal", "kind": "Literal",
"name": "first", "name": "first",
"value": 0 "value": 0
} }
], ],
v8 = [ v11 = [
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -104,72 +146,72 @@ v8 = [
"storageKey": null "storageKey": null
} }
], ],
v9 = { v12 = {
"alias": "evidencesInfos", "alias": "evidencesInfos",
"args": (v7/*: any*/), "args": (v10/*: any*/),
"concreteType": "EvidenceConnection", "concreteType": "EvidenceConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "evidences", "name": "evidences",
"plural": false, "plural": false,
"selections": (v8/*: any*/), "selections": (v11/*: any*/),
"storageKey": "evidences(first:0)" "storageKey": "evidences(first:0)"
}, },
v10 = { v13 = {
"alias": "risksInfos", "alias": "risksInfos",
"args": (v7/*: any*/), "args": (v10/*: any*/),
"concreteType": "RiskConnection", "concreteType": "RiskConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "risks", "name": "risks",
"plural": false, "plural": false,
"selections": (v8/*: any*/), "selections": (v11/*: any*/),
"storageKey": "risks(first:0)" "storageKey": "risks(first:0)"
}, },
v11 = { v14 = {
"alias": "controlsInfos", "alias": "controlsInfos",
"args": (v7/*: any*/), "args": (v10/*: any*/),
"concreteType": "ControlConnection", "concreteType": "ControlConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "controls", "name": "controls",
"plural": false, "plural": false,
"selections": (v8/*: any*/), "selections": (v11/*: any*/),
"storageKey": "controls(first:0)" "storageKey": "controls(first:0)"
}, },
v12 = { v15 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "__typename", "name": "__typename",
"storageKey": null "storageKey": null
}, },
v13 = [ v16 = [
{ {
"kind": "Literal", "kind": "Literal",
"name": "first", "name": "first",
"value": 100 "value": 100
} }
], ],
v14 = { v17 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "cursor", "name": "cursor",
"storageKey": null "storageKey": null
}, },
v15 = { v18 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "endCursor", "name": "endCursor",
"storageKey": null "storageKey": null
}, },
v16 = { v19 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "hasNextPage", "name": "hasNextPage",
"storageKey": null "storageKey": null
}, },
v17 = { v20 = {
"kind": "ClientExtension", "kind": "ClientExtension",
"selections": [ "selections": [
{ {
@@ -181,14 +223,14 @@ v17 = {
} }
] ]
}, },
v18 = [ v21 = [
{ {
"kind": "Literal", "kind": "Literal",
"name": "first", "name": "first",
"value": 20 "value": 20
} }
], ],
v19 = { v22 = {
"alias": null, "alias": null,
"args": null, "args": null,
"concreteType": "PageInfo", "concreteType": "PageInfo",
@@ -196,8 +238,8 @@ v19 = {
"name": "pageInfo", "name": "pageInfo",
"plural": false, "plural": false,
"selections": [ "selections": [
(v15/*: any*/), (v18/*: any*/),
(v16/*: any*/), (v19/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -215,7 +257,7 @@ v19 = {
], ],
"storageKey": null "storageKey": null
}, },
v20 = [ v23 = [
{ {
"kind": "Literal", "kind": "Literal",
"name": "first", "name": "first",
@@ -245,9 +287,12 @@ return {
(v4/*: any*/), (v4/*: any*/),
(v5/*: any*/), (v5/*: any*/),
(v6/*: any*/), (v6/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/), (v9/*: any*/),
(v10/*: any*/), (v12/*: any*/),
(v11/*: any*/), (v13/*: any*/),
(v14/*: any*/),
{ {
"args": null, "args": null,
"kind": "FragmentSpread", "kind": "FragmentSpread",
@@ -293,7 +338,7 @@ return {
"name": "node", "name": "node",
"plural": false, "plural": false,
"selections": [ "selections": [
(v12/*: any*/), (v15/*: any*/),
(v2/*: any*/), (v2/*: any*/),
{ {
"kind": "InlineFragment", "kind": "InlineFragment",
@@ -302,12 +347,15 @@ return {
(v4/*: any*/), (v4/*: any*/),
(v5/*: any*/), (v5/*: any*/),
(v6/*: any*/), (v6/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/), (v9/*: any*/),
(v10/*: any*/), (v12/*: any*/),
(v11/*: any*/), (v13/*: any*/),
(v14/*: any*/),
{ {
"alias": null, "alias": null,
"args": (v13/*: any*/), "args": (v16/*: any*/),
"concreteType": "RiskConnection", "concreteType": "RiskConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "risks", "name": "risks",
@@ -345,11 +393,11 @@ return {
"name": "residualRiskScore", "name": "residualRiskScore",
"storageKey": null "storageKey": null
}, },
(v12/*: any*/) (v15/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v14/*: any*/) (v17/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
@@ -361,18 +409,18 @@ return {
"name": "pageInfo", "name": "pageInfo",
"plural": false, "plural": false,
"selections": [ "selections": [
(v15/*: any*/), (v18/*: any*/),
(v16/*: any*/) (v19/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v17/*: any*/) (v20/*: any*/)
], ],
"storageKey": "risks(first:100)" "storageKey": "risks(first:100)"
}, },
{ {
"alias": null, "alias": null,
"args": (v13/*: any*/), "args": (v16/*: any*/),
"filters": null, "filters": null,
"handle": "connection", "handle": "connection",
"key": "Measure__risks", "key": "Measure__risks",
@@ -381,7 +429,7 @@ return {
}, },
{ {
"alias": null, "alias": null,
"args": (v18/*: any*/), "args": (v21/*: any*/),
"concreteType": "ControlConnection", "concreteType": "ControlConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "controls", "name": "controls",
@@ -425,22 +473,22 @@ return {
], ],
"storageKey": null "storageKey": null
}, },
(v12/*: any*/) (v15/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v14/*: any*/) (v17/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v19/*: any*/), (v22/*: any*/),
(v17/*: any*/) (v20/*: any*/)
], ],
"storageKey": "controls(first:20)" "storageKey": "controls(first:20)"
}, },
{ {
"alias": null, "alias": null,
"args": (v18/*: any*/), "args": (v21/*: any*/),
"filters": [ "filters": [
"orderBy", "orderBy",
"filter" "filter"
@@ -452,7 +500,7 @@ return {
}, },
{ {
"alias": null, "alias": null,
"args": (v20/*: any*/), "args": (v23/*: any*/),
"concreteType": "EvidenceConnection", "concreteType": "EvidenceConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "evidences", "name": "evidences",
@@ -522,22 +570,22 @@ return {
"name": "createdAt", "name": "createdAt",
"storageKey": null "storageKey": null
}, },
(v12/*: any*/) (v15/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v14/*: any*/) (v17/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v19/*: any*/), (v22/*: any*/),
(v17/*: any*/) (v20/*: any*/)
], ],
"storageKey": "evidences(first:50)" "storageKey": "evidences(first:50)"
}, },
{ {
"alias": null, "alias": null,
"args": (v20/*: any*/), "args": (v23/*: any*/),
"filters": [ "filters": [
"orderBy" "orderBy"
], ],
@@ -556,16 +604,16 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "fb510405c5934dbaf802c61c3ee8a547", "cacheID": "e168532dd6888e5d7ddfaca1dc5f188e",
"id": null, "id": null,
"metadata": {}, "metadata": {},
"name": "MeasureGraphNodeQuery", "name": "MeasureGraphNodeQuery",
"operationKind": "query", "operationKind": "query",
"text": "query MeasureGraphNodeQuery(\n $measureId: ID!\n) {\n node(id: $measureId) {\n __typename\n ... on Measure {\n id\n name\n description\n state\n category\n evidencesInfos: evidences(first: 0) {\n totalCount\n }\n risksInfos: risks(first: 0) {\n totalCount\n }\n controlsInfos: controls(first: 0) {\n totalCount\n }\n ...MeasureRisksTabFragment\n ...MeasureControlsTabFragment\n ...MeasureFormDialogMeasureFragment\n ...MeasureEvidencesTabFragment\n }\n id\n }\n}\n\nfragment LinkedControlsCardFragment on Control {\n id\n name\n sectionTitle\n framework {\n id\n name\n }\n}\n\nfragment LinkedRisksCardFragment on Risk {\n id\n name\n inherentRiskScore\n residualRiskScore\n}\n\nfragment MeasureControlsTabFragment on Measure {\n id\n controls(first: 20) {\n edges {\n node {\n id\n ...LinkedControlsCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment MeasureEvidencesTabFragment on Measure {\n id\n evidences(first: 50) {\n edges {\n node {\n id\n file {\n fileName\n mimeType\n size\n id\n }\n ...MeasureEvidencesTabFragment_evidence\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment MeasureEvidencesTabFragment_evidence on Evidence {\n id\n file {\n fileName\n mimeType\n size\n id\n }\n type\n createdAt\n}\n\nfragment MeasureFormDialogMeasureFragment on Measure {\n id\n description\n name\n category\n state\n}\n\nfragment MeasureRisksTabFragment on Measure {\n id\n risks(first: 100) {\n edges {\n node {\n id\n ...LinkedRisksCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n" "text": "query MeasureGraphNodeQuery(\n $measureId: ID!\n) {\n node(id: $measureId) {\n __typename\n ... on Measure {\n id\n name\n description\n state\n category\n canUpdate: permission(action: \"core:measure:update\")\n canDelete: permission(action: \"core:measure:delete\")\n canListTasks: permission(action: \"core:task:list\")\n evidencesInfos: evidences(first: 0) {\n totalCount\n }\n risksInfos: risks(first: 0) {\n totalCount\n }\n controlsInfos: controls(first: 0) {\n totalCount\n }\n ...MeasureRisksTabFragment\n ...MeasureControlsTabFragment\n ...MeasureFormDialogMeasureFragment\n ...MeasureEvidencesTabFragment\n }\n id\n }\n}\n\nfragment LinkedControlsCardFragment on Control {\n id\n name\n sectionTitle\n framework {\n id\n name\n }\n}\n\nfragment LinkedRisksCardFragment on Risk {\n id\n name\n inherentRiskScore\n residualRiskScore\n}\n\nfragment MeasureControlsTabFragment on Measure {\n id\n controls(first: 20) {\n edges {\n node {\n id\n ...LinkedControlsCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment MeasureEvidencesTabFragment on Measure {\n id\n evidences(first: 50) {\n edges {\n node {\n id\n file {\n fileName\n mimeType\n size\n id\n }\n ...MeasureEvidencesTabFragment_evidence\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n}\n\nfragment MeasureEvidencesTabFragment_evidence on Evidence {\n id\n file {\n fileName\n mimeType\n size\n id\n }\n type\n createdAt\n}\n\nfragment MeasureFormDialogMeasureFragment on Measure {\n id\n description\n name\n category\n state\n}\n\nfragment MeasureRisksTabFragment on Measure {\n id\n risks(first: 100) {\n edges {\n node {\n id\n ...LinkedRisksCardFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "fea0fb9de2bebb764995341906e0de58"; (node as any).hash = "0d55da2c159f16130dd3aaf232103b0f";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<1ee2176710f8c100a3d16e65be7a5f74>> * @generated SignedSource<<07e722a4da8a25668cd0e49cc07598f9>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -17,6 +17,7 @@ export type MeasureTasksTabQuery$variables = {
export type MeasureTasksTabQuery$data = { export type MeasureTasksTabQuery$data = {
readonly node: { readonly node: {
readonly __typename: "Measure"; readonly __typename: "Measure";
readonly canCreateTask: boolean;
readonly id: string; readonly id: string;
readonly tasks: { readonly tasks: {
readonly __id: string; readonly __id: string;
@@ -69,20 +70,33 @@ v3 = {
"storageKey": null "storageKey": null
}, },
v4 = { v4 = {
"alias": "canCreateTask",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:task:create"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:task:create\")"
},
v5 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "state", "name": "state",
"storageKey": null "storageKey": null
}, },
v5 = { v6 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
"name": "cursor", "name": "cursor",
"storageKey": null "storageKey": null
}, },
v6 = { v7 = {
"alias": null, "alias": null,
"args": null, "args": null,
"concreteType": "PageInfo", "concreteType": "PageInfo",
@@ -107,7 +121,7 @@ v6 = {
], ],
"storageKey": null "storageKey": null
}, },
v7 = { v8 = {
"kind": "ClientExtension", "kind": "ClientExtension",
"selections": [ "selections": [
{ {
@@ -119,14 +133,14 @@ v7 = {
} }
] ]
}, },
v8 = [ v9 = [
{ {
"kind": "Literal", "kind": "Literal",
"name": "first", "name": "first",
"value": 100 "value": 100
} }
], ],
v9 = { v10 = {
"alias": null, "alias": null,
"args": null, "args": null,
"kind": "ScalarField", "kind": "ScalarField",
@@ -155,6 +169,7 @@ return {
"kind": "InlineFragment", "kind": "InlineFragment",
"selections": [ "selections": [
(v3/*: any*/), (v3/*: any*/),
(v4/*: any*/),
{ {
"kind": "RequiredField", "kind": "RequiredField",
"field": { "field": {
@@ -184,7 +199,7 @@ return {
"plural": false, "plural": false,
"selections": [ "selections": [
(v3/*: any*/), (v3/*: any*/),
(v4/*: any*/), (v5/*: any*/),
{ {
"args": null, "args": null,
"kind": "FragmentSpread", "kind": "FragmentSpread",
@@ -199,14 +214,14 @@ return {
], ],
"storageKey": null "storageKey": null
}, },
(v5/*: any*/) (v6/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
"action": "THROW" "action": "THROW"
}, },
(v6/*: any*/), (v7/*: any*/),
(v7/*: any*/) (v8/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
@@ -244,9 +259,10 @@ return {
{ {
"kind": "InlineFragment", "kind": "InlineFragment",
"selections": [ "selections": [
(v4/*: any*/),
{ {
"alias": null, "alias": null,
"args": (v8/*: any*/), "args": (v9/*: any*/),
"concreteType": "TaskConnection", "concreteType": "TaskConnection",
"kind": "LinkedField", "kind": "LinkedField",
"name": "tasks", "name": "tasks",
@@ -269,7 +285,7 @@ return {
"plural": false, "plural": false,
"selections": [ "selections": [
(v3/*: any*/), (v3/*: any*/),
(v4/*: any*/), (v5/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -277,7 +293,7 @@ return {
"name": "description", "name": "description",
"storageKey": null "storageKey": null
}, },
(v9/*: any*/), (v10/*: any*/),
{ {
"alias": null, "alias": null,
"args": null, "args": null,
@@ -320,7 +336,7 @@ return {
"plural": false, "plural": false,
"selections": [ "selections": [
(v3/*: any*/), (v3/*: any*/),
(v9/*: any*/) (v10/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
@@ -354,18 +370,18 @@ return {
], ],
"storageKey": null "storageKey": null
}, },
(v5/*: any*/) (v6/*: any*/)
], ],
"storageKey": null "storageKey": null
}, },
(v6/*: any*/), (v7/*: any*/),
(v7/*: any*/) (v8/*: any*/)
], ],
"storageKey": "tasks(first:100)" "storageKey": "tasks(first:100)"
}, },
{ {
"alias": null, "alias": null,
"args": (v8/*: any*/), "args": (v9/*: any*/),
"filters": null, "filters": null,
"handle": "connection", "handle": "connection",
"key": "Measure__tasks", "key": "Measure__tasks",
@@ -382,7 +398,7 @@ return {
] ]
}, },
"params": { "params": {
"cacheID": "81a89b0ac44d316e2e580dd536be2812", "cacheID": "45dadcc414507aa7ccaab436cd5081c7",
"id": null, "id": null,
"metadata": { "metadata": {
"connection": [ "connection": [
@@ -399,11 +415,11 @@ return {
}, },
"name": "MeasureTasksTabQuery", "name": "MeasureTasksTabQuery",
"operationKind": "query", "operationKind": "query",
"text": "query MeasureTasksTabQuery(\n $measureId: ID!\n) {\n node(id: $measureId) {\n __typename\n ... on Measure {\n id\n tasks(first: 100) {\n edges {\n node {\n id\n state\n ...TaskFormDialogFragment\n ...TasksCard_TaskRowFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n\nfragment TaskFormDialogFragment on Task {\n id\n description\n name\n state\n timeEstimate\n deadline\n assignedTo {\n id\n }\n measure {\n id\n }\n}\n\nfragment TasksCard_TaskRowFragment on Task {\n id\n name\n state\n description\n timeEstimate\n deadline\n canUpdate: permission(action: \"core:task:update\")\n canDelete: permission(action: \"core:task:delete\")\n assignedTo {\n id\n fullName\n }\n measure {\n id\n name\n }\n}\n" "text": "query MeasureTasksTabQuery(\n $measureId: ID!\n) {\n node(id: $measureId) {\n __typename\n ... on Measure {\n id\n canCreateTask: permission(action: \"core:task:create\")\n tasks(first: 100) {\n edges {\n node {\n id\n state\n ...TaskFormDialogFragment\n ...TasksCard_TaskRowFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n\nfragment TaskFormDialogFragment on Task {\n id\n description\n name\n state\n timeEstimate\n deadline\n assignedTo {\n id\n }\n measure {\n id\n }\n}\n\nfragment TasksCard_TaskRowFragment on Task {\n id\n name\n state\n description\n timeEstimate\n deadline\n canUpdate: permission(action: \"core:task:update\")\n canDelete: permission(action: \"core:task:delete\")\n assignedTo {\n id\n fullName\n }\n measure {\n id\n name\n }\n}\n"
} }
}; };
})(); })();
(node as any).hash = "0e886bdfa0bcba6ec98758098e1ec0b5"; (node as any).hash = "a2dcb46b0d6a1a26b4e23db5c44c5d65";
export default node; export default node;

View File

@@ -1,5 +1,5 @@
/** /**
* @generated SignedSource<<7b45de7513dda2166217b9bd76716ece>> * @generated SignedSource<<2332fd6939942a5ee3d32511feffa42e>>
* @lightSyntaxTransform * @lightSyntaxTransform
* @nogrep * @nogrep
*/ */
@@ -16,6 +16,8 @@ export type MeasuresPageFragment$data = {
readonly __id: string; readonly __id: string;
readonly edges: ReadonlyArray<{ readonly edges: ReadonlyArray<{
readonly node: { readonly node: {
readonly canDelete: boolean;
readonly canUpdate: boolean;
readonly category: string; readonly category: string;
readonly id: string; readonly id: string;
readonly name: string; readonly name: string;
@@ -100,6 +102,32 @@ const node: ReaderFragment = {
"name": "state", "name": "state",
"storageKey": null "storageKey": null
}, },
{
"alias": "canUpdate",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:measure:update"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:measure:update\")"
},
{
"alias": "canDelete",
"args": [
{
"kind": "Literal",
"name": "action",
"value": "core:measure:delete"
}
],
"kind": "ScalarField",
"name": "permission",
"storageKey": "permission(action:\"core:measure:delete\")"
},
{ {
"args": null, "args": null,
"kind": "FragmentSpread", "kind": "FragmentSpread",
@@ -170,6 +198,6 @@ const node: ReaderFragment = {
"abstractKey": null "abstractKey": null
}; };
(node as any).hash = "31e13504f5bd52607fa81ac300e7303d"; (node as any).hash = "e9d543c6f68f5c205ba55f6d77206091";
export default node; export default node;

View File

@@ -17,7 +17,7 @@ import {
useConfirm, useConfirm,
useDialogRef, useDialogRef,
} from "@probo/ui"; } from "@probo/ui";
import { Fragment, use } from "react"; import { Fragment } from "react";
import { import {
graphql, graphql,
useFragment, useFragment,
@@ -30,7 +30,6 @@ import TaskFormDialog, {
} from "/components/tasks/TaskFormDialog"; } from "/components/tasks/TaskFormDialog";
import { updateStoreCounter } from "/hooks/useMutationWithIncrement"; import { updateStoreCounter } from "/hooks/useMutationWithIncrement";
import { useOrganizationId } from "/hooks/useOrganizationId"; import { useOrganizationId } from "/hooks/useOrganizationId";
import { PermissionsContext } from "/providers/PermissionsContext";
import type { TasksCard_TaskRowFragment$key } from "/__generated__/core/TasksCard_TaskRowFragment.graphql"; import type { TasksCard_TaskRowFragment$key } from "/__generated__/core/TasksCard_TaskRowFragment.graphql";
import type { TasksPageFragment$data } from "/__generated__/core/TasksPageFragment.graphql"; import type { TasksPageFragment$data } from "/__generated__/core/TasksPageFragment.graphql";
import type { MeasureTasksTabQuery$data } from "/__generated__/core/MeasureTasksTabQuery.graphql"; import type { MeasureTasksTabQuery$data } from "/__generated__/core/MeasureTasksTabQuery.graphql";
@@ -162,7 +161,6 @@ function TaskRow(props: TaskRowProps) {
const confirm = useConfirm(); const confirm = useConfirm();
const [deleteTask] = useMutation(deleteMutation); const [deleteTask] = useMutation(deleteMutation);
const params = useParams<{ measureId?: string }>(); const params = useParams<{ measureId?: string }>();
const { isAuthorized } = use(PermissionsContext);
const relayEnv = useRelayEnvironment(); const relayEnv = useRelayEnvironment();
const { canUpdate, canDelete, ...task } = const { canUpdate, canDelete, ...task } =
@@ -271,7 +269,7 @@ function TaskRow(props: TaskRowProps) {
{isUpdating && <Spinner size={16} />} {isUpdating && <Spinner size={16} />}
{(canUpdate || canDelete) && ( {(canUpdate || canDelete) && (
<ActionDropdown> <ActionDropdown>
{isAuthorized("Task", "updateTask") && ( {canUpdate && (
<DropdownItem <DropdownItem
icon={IconPencil} icon={IconPencil}
onClick={() => dialogRef.current?.open()} onClick={() => dialogRef.current?.open()}
@@ -279,7 +277,7 @@ function TaskRow(props: TaskRowProps) {
{__("Edit")} {__("Edit")}
</DropdownItem> </DropdownItem>
)} )}
{isAuthorized("Task", "deleteTask") && ( {canDelete && (
<DropdownItem <DropdownItem
variant="danger" variant="danger"
icon={IconTrashCan} icon={IconTrashCan}

View File

@@ -5,9 +5,13 @@ import type { MeasureGraphDeleteMutation } from "/__generated__/core/MeasureGrap
export const measuresQuery = graphql` export const measuresQuery = graphql`
query MeasureGraphListQuery($organizationId: ID!) { query MeasureGraphListQuery($organizationId: ID!) {
organization: node(id: $organizationId) { organization: node(id: $organizationId) @required(action: THROW) {
id __typename
...MeasuresPageFragment ... on Organization {
id
canCreateMeasure: permission(action: "core:measure:create")
...MeasuresPageFragment
}
} }
} }
`; `;
@@ -46,6 +50,9 @@ export const measureNodeQuery = graphql`
description description
state state
category category
canUpdate: permission(action: "core:measure:update")
canDelete: permission(action: "core:measure:delete")
canListTasks: permission(action: "core:task:list")
evidencesInfos: evidences(first: 0) { evidencesInfos: evidences(first: 0) {
totalCount totalCount
} }

View File

@@ -45,8 +45,7 @@ import {
sprintf, sprintf,
} from "@probo/helpers"; } from "@probo/helpers";
import MeasureFormDialog from "./dialog/MeasureFormDialog"; import MeasureFormDialog from "./dialog/MeasureFormDialog";
import { Suspense, use } from "react"; import { Suspense } from "react";
import { PermissionsContext } from "/providers/PermissionsContext";
const tasksCountQuery = graphql` const tasksCountQuery = graphql`
query MeasureDetailPageTasksCountQuery($measureId: ID!) { query MeasureDetailPageTasksCountQuery($measureId: ID!) {
@@ -83,14 +82,12 @@ export default function MeasureDetailPage(props: Props) {
const navigate = useNavigate(); const navigate = useNavigate();
const confirm = useConfirm(); const confirm = useConfirm();
const [updateMeasure, isUpdating] = useUpdateMeasure(); const [updateMeasure, isUpdating] = useUpdateMeasure();
const { isAuthorized } = use(PermissionsContext);
if (!measureId) { if (!measureId) {
throw new Error( throw new Error(
"Cannot load measure detail page without measureId parameter", "Cannot load measure detail page without measureId parameter",
); );
} }
const canViewTasks = isAuthorized("Measure", "listTasks");
const evidencesCount = measure.evidencesInfos?.totalCount ?? 0; const evidencesCount = measure.evidencesInfos?.totalCount ?? 0;
const controlsCount = measure.controlsInfos?.totalCount ?? 0; const controlsCount = measure.controlsInfos?.totalCount ?? 0;
const risksCount = measure.risksInfos?.totalCount ?? 0; const risksCount = measure.risksInfos?.totalCount ?? 0;
@@ -160,7 +157,7 @@ export default function MeasureDetailPage(props: Props) {
/> />
<PageHeader title={measure.name} description={measure.description}> <PageHeader title={measure.name} description={measure.description}>
{isAuthorized("Measure", "updateMeasure") && ( {measure.canUpdate && (
<> <>
<MeasureFormDialog measure={measure}> <MeasureFormDialog measure={measure}>
<Button variant="secondary" icon={IconPencil}> <Button variant="secondary" icon={IconPencil}>
@@ -183,7 +180,7 @@ export default function MeasureDetailPage(props: Props) {
</Select> </Select>
</> </>
)} )}
{isAuthorized("Measure", "deleteMeasure") && ( {measure.canDelete && (
<ActionDropdown variant="secondary"> <ActionDropdown variant="secondary">
<DropdownItem <DropdownItem
variant="danger" variant="danger"
@@ -204,7 +201,7 @@ export default function MeasureDetailPage(props: Props) {
{__("Evidences")} {__("Evidences")}
<TabBadge>{evidencesCount}</TabBadge> <TabBadge>{evidencesCount}</TabBadge>
</TabLink> </TabLink>
{canViewTasks && ( {measure.canListTasks && (
<TabLink <TabLink
to={`/organizations/${organizationId}/measures/${measureId}/tasks`} to={`/organizations/${organizationId}/measures/${measureId}/tasks`}
> >

View File

@@ -39,7 +39,7 @@ import type {
MeasuresPageFragment$key, MeasuresPageFragment$key,
} from "/__generated__/core/MeasuresPageFragment.graphql"; } from "/__generated__/core/MeasuresPageFragment.graphql";
import { groupBy, objectKeys, slugify, sprintf } from "@probo/helpers"; import { groupBy, objectKeys, slugify, sprintf } from "@probo/helpers";
import { useMemo, useRef, useState, type ChangeEventHandler, use } from "react"; import { useMemo, useRef, useState, type ChangeEventHandler } from "react";
import type { NodeOf } from "/types"; import type { NodeOf } from "/types";
import type { MeasuresPageImportMutation } from "/__generated__/core/MeasuresPageImportMutation.graphql"; import type { MeasuresPageImportMutation } from "/__generated__/core/MeasuresPageImportMutation.graphql";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
@@ -47,7 +47,6 @@ import { useOrganizationId } from "/hooks/useOrganizationId";
import { Link, useParams } from "react-router"; import { Link, useParams } from "react-router";
import MeasureFormDialog from "./dialog/MeasureFormDialog"; import MeasureFormDialog from "./dialog/MeasureFormDialog";
import { usePageTitle } from "@probo/hooks"; import { usePageTitle } from "@probo/hooks";
import { PermissionsContext } from "/providers/PermissionsContext";
type Props = { type Props = {
queryRef: PreloadedQuery<MeasureGraphListQuery>; queryRef: PreloadedQuery<MeasureGraphListQuery>;
@@ -63,6 +62,8 @@ const measuresFragment = graphql`
name name
category category
state state
canUpdate: permission(action: "core:measure:update")
canDelete: permission(action: "core:measure:delete")
...MeasureFormDialogMeasureFragment ...MeasureFormDialogMeasureFragment
} }
} }
@@ -90,11 +91,13 @@ const importMeasuresMutation = graphql`
export default function MeasuresPage(props: Props) { export default function MeasuresPage(props: Props) {
const { __ } = useTranslate(); const { __ } = useTranslate();
const { isAuthorized } = use(PermissionsContext);
const organization = usePreloadedQuery( const organization = usePreloadedQuery(
measuresQuery, measuresQuery,
props.queryRef, props.queryRef,
).organization; ).organization;
if (organization.__typename !== "Organization") {
throw new Error("invalid node type");
}
const data = useFragment<MeasuresPageFragment$key>( const data = useFragment<MeasuresPageFragment$key>(
measuresFragment, measuresFragment,
organization, organization,
@@ -114,10 +117,6 @@ export default function MeasuresPage(props: Props) {
const importFileRef = useRef<HTMLInputElement>(null); const importFileRef = useRef<HTMLInputElement>(null);
usePageTitle(__("Measures")); usePageTitle(__("Measures"));
const hasAnyAction =
isAuthorized("Measure", "updateMeasure") ||
isAuthorized("Measure", "deleteMeasure");
const handleImport: ChangeEventHandler<HTMLInputElement> = (event) => { const handleImport: ChangeEventHandler<HTMLInputElement> = (event) => {
const file = event.target.files?.[0]; const file = event.target.files?.[0];
if (!file) { if (!file) {
@@ -148,7 +147,7 @@ export default function MeasuresPage(props: Props) {
"Measures are actions taken to reduce the risk. Add them to track their implementation status.", "Measures are actions taken to reduce the risk. Add them to track their implementation status.",
)} )}
> >
{isAuthorized("Organization", "createMeasure") && ( {organization.canCreateMeasure && (
<> <>
<FileButton <FileButton
ref={importFileRef} ref={importFileRef}
@@ -175,7 +174,6 @@ export default function MeasuresPage(props: Props) {
category={category} category={category}
measures={measuresPerCategory[category]} measures={measuresPerCategory[category]}
connectionId={connectionId} connectionId={connectionId}
hasAnyAction={hasAnyAction}
/> />
))} ))}
</div> </div>
@@ -186,7 +184,6 @@ type CategoryProps = {
category: string; category: string;
measures: NodeOf<MeasuresPageFragment$data["measures"]>[]; measures: NodeOf<MeasuresPageFragment$data["measures"]>[];
connectionId: string; connectionId: string;
hasAnyAction: boolean;
}; };
function Category(props: CategoryProps) { function Category(props: CategoryProps) {
@@ -230,7 +227,9 @@ function Category(props: CategoryProps) {
<Tr> <Tr>
<Th>{__("Measure")}</Th> <Th>{__("Measure")}</Th>
<Th>{__("State")}</Th> <Th>{__("State")}</Th>
{props.hasAnyAction && <Th></Th>} {measures.some(
({ canUpdate, canDelete }) => canUpdate || canDelete,
) && <Th></Th>}
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
@@ -239,7 +238,7 @@ function Category(props: CategoryProps) {
key={measure.id} key={measure.id}
measure={measure} measure={measure}
connectionId={props.connectionId} connectionId={props.connectionId}
hasAnyAction={props.hasAnyAction} hasAnyAction={measure.canUpdate || measure.canDelete}
/> />
))} ))}
</Tbody> </Tbody>
@@ -271,7 +270,6 @@ function MeasureRow(props: MeasureRowProps) {
const [deleteMeasure, isDeleting] = useDeleteMeasureMutation(); const [deleteMeasure, isDeleting] = useDeleteMeasureMutation();
const confirm = useConfirm(); const confirm = useConfirm();
const organizationId = useOrganizationId(); const organizationId = useOrganizationId();
const { isAuthorized } = use(PermissionsContext);
const onDelete = () => { const onDelete = () => {
confirm( confirm(
@@ -306,10 +304,10 @@ function MeasureRow(props: MeasureRowProps) {
<Td width={120}> <Td width={120}>
<MeasureBadge state={props.measure.state} /> <MeasureBadge state={props.measure.state} />
</Td> </Td>
{props.hasAnyAction && ( {(props.measure.canUpdate || props.measure.canDelete) && (
<Td noLink width={50} className="text-end"> <Td noLink width={50} className="text-end">
<ActionDropdown> <ActionDropdown>
{isAuthorized("Measure", "updateMeasure") && ( {props.measure.canUpdate && (
<DropdownItem <DropdownItem
icon={IconPencil} icon={IconPencil}
onClick={() => dialogRef.current?.open()} onClick={() => dialogRef.current?.open()}
@@ -317,7 +315,7 @@ function MeasureRow(props: MeasureRowProps) {
{__("Edit")} {__("Edit")}
</DropdownItem> </DropdownItem>
)} )}
{isAuthorized("Measure", "deleteMeasure") && ( {props.measure.canDelete && (
<DropdownItem <DropdownItem
onClick={onDelete} onClick={onDelete}
disabled={isDeleting} disabled={isDeleting}

View File

@@ -13,6 +13,7 @@ const tasksQuery = graphql`
__typename __typename
... on Measure { ... on Measure {
id id
canCreateTask: permission(action: "core:task:create")
tasks(first: 100) tasks(first: 100)
@connection(key: "Measure__tasks") @connection(key: "Measure__tasks")
@required(action: THROW) { @required(action: THROW) {
@@ -47,15 +48,17 @@ export default function MeasureTasksTab() {
return ( return (
<div className="relative"> <div className="relative">
<TasksCard connectionId={connectionId} tasks={node.tasks.edges} /> <TasksCard connectionId={connectionId} tasks={node.tasks.edges} />
<TaskFormDialog connection={connectionId} measureId={measure.id}> {node.canCreateTask && (
<Button <TaskFormDialog connection={connectionId} measureId={measure.id}>
variant="secondary" <Button
icon={IconPlusLarge} variant="secondary"
className="absolute top-3 right-6" icon={IconPlusLarge}
> className="absolute top-3 right-6"
{__("New task")} >
</Button> {__("New task")}
</TaskFormDialog> </Button>
</TaskFormDialog>
)}
</div> </div>
); );
} }