Fix measure tasks page permissions handling
Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
@@ -45,8 +45,7 @@ import {
|
||||
sprintf,
|
||||
} from "@probo/helpers";
|
||||
import MeasureFormDialog from "./dialog/MeasureFormDialog";
|
||||
import { Suspense, use } from "react";
|
||||
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||
import { Suspense } from "react";
|
||||
|
||||
const tasksCountQuery = graphql`
|
||||
query MeasureDetailPageTasksCountQuery($measureId: ID!) {
|
||||
@@ -83,14 +82,12 @@ export default function MeasureDetailPage(props: Props) {
|
||||
const navigate = useNavigate();
|
||||
const confirm = useConfirm();
|
||||
const [updateMeasure, isUpdating] = useUpdateMeasure();
|
||||
const { isAuthorized } = use(PermissionsContext);
|
||||
if (!measureId) {
|
||||
throw new Error(
|
||||
"Cannot load measure detail page without measureId parameter",
|
||||
);
|
||||
}
|
||||
|
||||
const canViewTasks = isAuthorized("Measure", "listTasks");
|
||||
const evidencesCount = measure.evidencesInfos?.totalCount ?? 0;
|
||||
const controlsCount = measure.controlsInfos?.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}>
|
||||
{isAuthorized("Measure", "updateMeasure") && (
|
||||
{measure.canUpdate && (
|
||||
<>
|
||||
<MeasureFormDialog measure={measure}>
|
||||
<Button variant="secondary" icon={IconPencil}>
|
||||
@@ -183,7 +180,7 @@ export default function MeasureDetailPage(props: Props) {
|
||||
</Select>
|
||||
</>
|
||||
)}
|
||||
{isAuthorized("Measure", "deleteMeasure") && (
|
||||
{measure.canDelete && (
|
||||
<ActionDropdown variant="secondary">
|
||||
<DropdownItem
|
||||
variant="danger"
|
||||
@@ -204,7 +201,7 @@ export default function MeasureDetailPage(props: Props) {
|
||||
{__("Evidences")}
|
||||
<TabBadge>{evidencesCount}</TabBadge>
|
||||
</TabLink>
|
||||
{canViewTasks && (
|
||||
{measure.canListTasks && (
|
||||
<TabLink
|
||||
to={`/organizations/${organizationId}/measures/${measureId}/tasks`}
|
||||
>
|
||||
|
||||
@@ -39,7 +39,7 @@ import type {
|
||||
MeasuresPageFragment$key,
|
||||
} from "/__generated__/core/MeasuresPageFragment.graphql";
|
||||
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 { MeasuresPageImportMutation } from "/__generated__/core/MeasuresPageImportMutation.graphql";
|
||||
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
|
||||
@@ -47,7 +47,6 @@ import { useOrganizationId } from "/hooks/useOrganizationId";
|
||||
import { Link, useParams } from "react-router";
|
||||
import MeasureFormDialog from "./dialog/MeasureFormDialog";
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { PermissionsContext } from "/providers/PermissionsContext";
|
||||
|
||||
type Props = {
|
||||
queryRef: PreloadedQuery<MeasureGraphListQuery>;
|
||||
@@ -63,6 +62,8 @@ const measuresFragment = graphql`
|
||||
name
|
||||
category
|
||||
state
|
||||
canUpdate: permission(action: "core:measure:update")
|
||||
canDelete: permission(action: "core:measure:delete")
|
||||
...MeasureFormDialogMeasureFragment
|
||||
}
|
||||
}
|
||||
@@ -90,11 +91,13 @@ const importMeasuresMutation = graphql`
|
||||
|
||||
export default function MeasuresPage(props: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { isAuthorized } = use(PermissionsContext);
|
||||
const organization = usePreloadedQuery(
|
||||
measuresQuery,
|
||||
props.queryRef,
|
||||
).organization;
|
||||
if (organization.__typename !== "Organization") {
|
||||
throw new Error("invalid node type");
|
||||
}
|
||||
const data = useFragment<MeasuresPageFragment$key>(
|
||||
measuresFragment,
|
||||
organization,
|
||||
@@ -114,10 +117,6 @@ export default function MeasuresPage(props: Props) {
|
||||
const importFileRef = useRef<HTMLInputElement>(null);
|
||||
usePageTitle(__("Measures"));
|
||||
|
||||
const hasAnyAction =
|
||||
isAuthorized("Measure", "updateMeasure") ||
|
||||
isAuthorized("Measure", "deleteMeasure");
|
||||
|
||||
const handleImport: ChangeEventHandler<HTMLInputElement> = (event) => {
|
||||
const file = event.target.files?.[0];
|
||||
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.",
|
||||
)}
|
||||
>
|
||||
{isAuthorized("Organization", "createMeasure") && (
|
||||
{organization.canCreateMeasure && (
|
||||
<>
|
||||
<FileButton
|
||||
ref={importFileRef}
|
||||
@@ -175,7 +174,6 @@ export default function MeasuresPage(props: Props) {
|
||||
category={category}
|
||||
measures={measuresPerCategory[category]}
|
||||
connectionId={connectionId}
|
||||
hasAnyAction={hasAnyAction}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -186,7 +184,6 @@ type CategoryProps = {
|
||||
category: string;
|
||||
measures: NodeOf<MeasuresPageFragment$data["measures"]>[];
|
||||
connectionId: string;
|
||||
hasAnyAction: boolean;
|
||||
};
|
||||
|
||||
function Category(props: CategoryProps) {
|
||||
@@ -230,7 +227,9 @@ function Category(props: CategoryProps) {
|
||||
<Tr>
|
||||
<Th>{__("Measure")}</Th>
|
||||
<Th>{__("State")}</Th>
|
||||
{props.hasAnyAction && <Th></Th>}
|
||||
{measures.some(
|
||||
({ canUpdate, canDelete }) => canUpdate || canDelete,
|
||||
) && <Th></Th>}
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
@@ -239,7 +238,7 @@ function Category(props: CategoryProps) {
|
||||
key={measure.id}
|
||||
measure={measure}
|
||||
connectionId={props.connectionId}
|
||||
hasAnyAction={props.hasAnyAction}
|
||||
hasAnyAction={measure.canUpdate || measure.canDelete}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
@@ -271,7 +270,6 @@ function MeasureRow(props: MeasureRowProps) {
|
||||
const [deleteMeasure, isDeleting] = useDeleteMeasureMutation();
|
||||
const confirm = useConfirm();
|
||||
const organizationId = useOrganizationId();
|
||||
const { isAuthorized } = use(PermissionsContext);
|
||||
|
||||
const onDelete = () => {
|
||||
confirm(
|
||||
@@ -306,10 +304,10 @@ function MeasureRow(props: MeasureRowProps) {
|
||||
<Td width={120}>
|
||||
<MeasureBadge state={props.measure.state} />
|
||||
</Td>
|
||||
{props.hasAnyAction && (
|
||||
{(props.measure.canUpdate || props.measure.canDelete) && (
|
||||
<Td noLink width={50} className="text-end">
|
||||
<ActionDropdown>
|
||||
{isAuthorized("Measure", "updateMeasure") && (
|
||||
{props.measure.canUpdate && (
|
||||
<DropdownItem
|
||||
icon={IconPencil}
|
||||
onClick={() => dialogRef.current?.open()}
|
||||
@@ -317,7 +315,7 @@ function MeasureRow(props: MeasureRowProps) {
|
||||
{__("Edit")}
|
||||
</DropdownItem>
|
||||
)}
|
||||
{isAuthorized("Measure", "deleteMeasure") && (
|
||||
{props.measure.canDelete && (
|
||||
<DropdownItem
|
||||
onClick={onDelete}
|
||||
disabled={isDeleting}
|
||||
|
||||
@@ -13,6 +13,7 @@ const tasksQuery = graphql`
|
||||
__typename
|
||||
... on Measure {
|
||||
id
|
||||
canCreateTask: permission(action: "core:task:create")
|
||||
tasks(first: 100)
|
||||
@connection(key: "Measure__tasks")
|
||||
@required(action: THROW) {
|
||||
@@ -47,15 +48,17 @@ export default function MeasureTasksTab() {
|
||||
return (
|
||||
<div className="relative">
|
||||
<TasksCard connectionId={connectionId} tasks={node.tasks.edges} />
|
||||
<TaskFormDialog connection={connectionId} measureId={measure.id}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon={IconPlusLarge}
|
||||
className="absolute top-3 right-6"
|
||||
>
|
||||
{__("New task")}
|
||||
</Button>
|
||||
</TaskFormDialog>
|
||||
{node.canCreateTask && (
|
||||
<TaskFormDialog connection={connectionId} measureId={measure.id}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon={IconPlusLarge}
|
||||
className="absolute top-3 right-6"
|
||||
>
|
||||
{__("New task")}
|
||||
</Button>
|
||||
</TaskFormDialog>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user