implement workflow permissions
This commit is contained in:
@@ -6,10 +6,12 @@ import Layout from "@/components/High/Layout";
|
||||
import { ApprovalWorkflow, EditableApprovalWorkflow, EditableWorkflowStep, getUserTypeLabelShort } from "@/interfaces/approval.workflow";
|
||||
import { CorporateUser, DeveloperUser, MasterCorporateUser, TeacherUser, User } from "@/interfaces/user";
|
||||
import { sessionOptions } from "@/lib/session";
|
||||
import { redirect, serialize } from "@/utils";
|
||||
import { findBy, redirect, serialize } from "@/utils";
|
||||
import { requestUser } from "@/utils/api";
|
||||
import { getApprovalWorkflow } from "@/utils/approval.workflows.be";
|
||||
import { getEntityWithRoles } from "@/utils/entities.be";
|
||||
import { shouldRedirectHome } from "@/utils/navigation.disabled";
|
||||
import { doesEntityAllow } from "@/utils/permissions";
|
||||
import { getEntityUsers } from "@/utils/users.be";
|
||||
import axios from "axios";
|
||||
import { LayoutGroup, motion } from "framer-motion";
|
||||
@@ -30,9 +32,12 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, params }
|
||||
const { id } = params as { id: string };
|
||||
|
||||
const workflow: ApprovalWorkflow | null = await getApprovalWorkflow("active-workflows", id);
|
||||
if (!workflow) return redirect("/approval-workflows");
|
||||
|
||||
if (!workflow)
|
||||
return redirect("/approval-workflows")
|
||||
const entityWithRole = await getEntityWithRoles(workflow.entityId);
|
||||
if (!entityWithRole) return redirect("/approval-workflows");
|
||||
|
||||
if (!doesEntityAllow(user, entityWithRole, "edit_workflow")) return redirect("/approval-workflows");
|
||||
|
||||
return {
|
||||
props: serialize({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Tip from "@/components/ApprovalWorkflows/Tip";
|
||||
import WorkflowForm from "@/components/ApprovalWorkflows/WorkflowForm";
|
||||
import Layout from "@/components/High/Layout";
|
||||
import Button from "@/components/Low/Button";
|
||||
import Input from "@/components/Low/Input";
|
||||
import Select from "@/components/Low/Select";
|
||||
@@ -8,11 +7,13 @@ import { ApprovalWorkflow, EditableApprovalWorkflow } from "@/interfaces/approva
|
||||
import { Entity } from "@/interfaces/entity";
|
||||
import { CorporateUser, DeveloperUser, MasterCorporateUser, TeacherUser, User } from "@/interfaces/user";
|
||||
import { sessionOptions } from "@/lib/session";
|
||||
import { redirect, serialize } from "@/utils";
|
||||
import { mapBy, redirect, serialize } from "@/utils";
|
||||
import { requestUser } from "@/utils/api";
|
||||
import { getApprovalWorkflowsByEntities } from "@/utils/approval.workflows.be";
|
||||
import { getEntities } from "@/utils/entities.be";
|
||||
import { getEntitiesWithRoles } from "@/utils/entities.be";
|
||||
import { shouldRedirectHome } from "@/utils/navigation.disabled";
|
||||
import { findAllowedEntities } from "@/utils/permissions";
|
||||
import { isAdmin } from "@/utils/users";
|
||||
import { getEntitiesUsers } from "@/utils/users.be";
|
||||
import axios from "axios";
|
||||
import { AnimatePresence, LayoutGroup, motion } from "framer-motion";
|
||||
@@ -31,10 +32,12 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
||||
const user = await requestUser(req, res)
|
||||
if (!user) return redirect("/login")
|
||||
|
||||
if (shouldRedirectHome(user) || !["admin", "developer", "corporate", "mastercorporate"].includes(user.type))
|
||||
if (shouldRedirectHome(user) || !["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type))
|
||||
return redirect("/")
|
||||
|
||||
const userEntitiesWithLabel = await getEntities(user.entities.map(entity => entity.id));
|
||||
const entityIDS = mapBy(user.entities, "id");
|
||||
const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : entityIDS);
|
||||
const userEntitiesWithLabel = findAllowedEntities(user, entities, "configure_workflows");
|
||||
|
||||
const allConfiguredWorkflows = await getApprovalWorkflowsByEntities("configured-workflows", userEntitiesWithLabel.map(entity => entity.id));
|
||||
|
||||
|
||||
@@ -4,57 +4,34 @@ import Button from "@/components/Low/Button";
|
||||
import Input from "@/components/Low/Input";
|
||||
import Select from "@/components/Low/Select";
|
||||
import useApprovalWorkflows from "@/hooks/useApprovalWorkflows";
|
||||
import {useAllowedEntities, useAllowedEntitiesSomePermissions, useEntityPermission} from "@/hooks/useEntityPermissions";
|
||||
import {Module, ModuleTypeLabels} from "@/interfaces";
|
||||
import {ApprovalWorkflow, ApprovalWorkflowStatus, ApprovalWorkflowStatusLabel, StepTypeLabel} from "@/interfaces/approval.workflow";
|
||||
import {Entity, EntityWithRoles} from "@/interfaces/entity";
|
||||
import {User} from "@/interfaces/user";
|
||||
import {sessionOptions} from "@/lib/session";
|
||||
import {redirect, serialize} from "@/utils";
|
||||
import {requestUser} from "@/utils/api";
|
||||
import {getApprovalWorkflows} from "@/utils/approval.workflows.be";
|
||||
import {getEntities, getEntitiesWithRoles} from "@/utils/entities.be";
|
||||
import {shouldRedirectHome} from "@/utils/navigation.disabled";
|
||||
import {findAllowedEntities} from "@/utils/permissions";
|
||||
import {getSpecificUsers} from "@/utils/users.be";
|
||||
import {createColumnHelper, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table";
|
||||
import { useAllowedEntities, useAllowedEntitiesSomePermissions, useEntityPermission } from "@/hooks/useEntityPermissions";
|
||||
import { Module, ModuleTypeLabels } from "@/interfaces";
|
||||
import { ApprovalWorkflow, ApprovalWorkflowStatus, ApprovalWorkflowStatusLabel, StepTypeLabel } from "@/interfaces/approval.workflow";
|
||||
import { Entity, EntityWithRoles } from "@/interfaces/entity";
|
||||
import { User } from "@/interfaces/user";
|
||||
import { sessionOptions } from "@/lib/session";
|
||||
import { mapBy, redirect, serialize } from "@/utils";
|
||||
import { requestUser } from "@/utils/api";
|
||||
import { getApprovalWorkflows } from "@/utils/approval.workflows.be";
|
||||
import { getEntities, getEntitiesWithRoles } from "@/utils/entities.be";
|
||||
import { shouldRedirectHome } from "@/utils/navigation.disabled";
|
||||
import { doesEntityAllow, findAllowedEntities } from "@/utils/permissions";
|
||||
import { isAdmin } from "@/utils/users";
|
||||
import { getSpecificUsers } from "@/utils/users.be";
|
||||
import { createColumnHelper, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||
import axios from "axios";
|
||||
import clsx from "clsx";
|
||||
import {withIronSessionSsr} from "iron-session/next";
|
||||
import { withIronSessionSsr } from "iron-session/next";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import {useEffect, useState} from "react";
|
||||
import {BsTrash} from "react-icons/bs";
|
||||
import {FaRegEdit} from "react-icons/fa";
|
||||
import {IoIosAddCircleOutline} from "react-icons/io";
|
||||
import {toast, ToastContainer} from "react-toastify";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { BsTrash } from "react-icons/bs";
|
||||
import { FaRegEdit } from "react-icons/fa";
|
||||
import { IoIosAddCircleOutline } from "react-icons/io";
|
||||
import { toast, ToastContainer } from "react-toastify";
|
||||
|
||||
const columnHelper = createColumnHelper<ApprovalWorkflow>();
|
||||
|
||||
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
|
||||
const user = await requestUser(req, res);
|
||||
if (!user) return redirect("/login");
|
||||
|
||||
if (shouldRedirectHome(user) || !["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type)) return redirect("/");
|
||||
|
||||
const workflows = await getApprovalWorkflows("active-workflows");
|
||||
|
||||
const allAssigneeIds: string[] = [...new Set(workflows.map((workflow) => workflow.steps.map((step) => step.assignees).flat()).flat())];
|
||||
|
||||
const entities = await getEntitiesWithRoles(user.entities.map((entity) => entity.id));
|
||||
const allowedEntities = findAllowedEntities(user, entities, "view_workflows");
|
||||
|
||||
return {
|
||||
props: serialize({
|
||||
user,
|
||||
initialWorkflows: workflows,
|
||||
workflowsAssignees: await getSpecificUsers(allAssigneeIds),
|
||||
userEntitiesWithLabel: entities,
|
||||
}),
|
||||
};
|
||||
}, sessionOptions);
|
||||
|
||||
const StatusClassNames: {[key in ApprovalWorkflowStatus]: string} = {
|
||||
const StatusClassNames: { [key in ApprovalWorkflowStatus]: string } = {
|
||||
approved:
|
||||
"bg-green-100 text-green-800 border border-green-300 before:content-[''] before:w-2 before:h-2 before:bg-green-500 before:rounded-full before:inline-block before:mr-2",
|
||||
pending:
|
||||
@@ -84,6 +61,40 @@ const STATUS_OPTIONS = [
|
||||
},
|
||||
];
|
||||
|
||||
const columnHelper = createColumnHelper<ApprovalWorkflow>();
|
||||
|
||||
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
||||
const user = await requestUser(req, res);
|
||||
if (!user) return redirect("/login");
|
||||
|
||||
if (shouldRedirectHome(user) || !["admin", "developer", "teacher", "corporate", "mastercorporate"].includes(user.type)) return redirect("/");
|
||||
|
||||
const workflows = await getApprovalWorkflows("active-workflows");
|
||||
|
||||
const allAssigneeIds: string[] = [
|
||||
...new Set(
|
||||
workflows
|
||||
.map(workflow => workflow.steps
|
||||
.map(step => step.assignees)
|
||||
.flat()
|
||||
).flat()
|
||||
)
|
||||
];
|
||||
|
||||
const entityIDS = mapBy(user.entities, "id");
|
||||
const entities = await getEntitiesWithRoles(isAdmin(user) ? undefined : entityIDS);
|
||||
const allowedEntities = findAllowedEntities(user, entities, "view_workflows");
|
||||
|
||||
return {
|
||||
props: serialize({
|
||||
user,
|
||||
initialWorkflows: workflows,
|
||||
workflowsAssignees: await getSpecificUsers(allAssigneeIds),
|
||||
userEntitiesWithLabel: allowedEntities,
|
||||
}),
|
||||
};
|
||||
}, sessionOptions);
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
initialWorkflows: ApprovalWorkflow[];
|
||||
@@ -91,8 +102,8 @@ interface Props {
|
||||
userEntitiesWithLabel: EntityWithRoles[];
|
||||
}
|
||||
|
||||
export default function ApprovalWorkflows({user, initialWorkflows, workflowsAssignees, userEntitiesWithLabel}: Props) {
|
||||
const {workflows, reload} = useApprovalWorkflows();
|
||||
export default function ApprovalWorkflows({ user, initialWorkflows, workflowsAssignees, userEntitiesWithLabel }: Props) {
|
||||
const { workflows, reload } = useApprovalWorkflows();
|
||||
const currentWorkflows = workflows || initialWorkflows;
|
||||
|
||||
const [filteredWorkflows, setFilteredWorkflows] = useState<ApprovalWorkflow[]>([]);
|
||||
@@ -101,8 +112,10 @@ export default function ApprovalWorkflows({user, initialWorkflows, workflowsAssi
|
||||
const [entityFilter, setEntityFilter] = useState<CustomEntity>(undefined);
|
||||
const [nameFilter, setNameFilter] = useState<string>("");
|
||||
|
||||
const allowedEntities = useAllowedEntities(user, userEntitiesWithLabel, "view_workflows");
|
||||
const allowedSomeEntities = useAllowedEntitiesSomePermissions(user, userEntitiesWithLabel, ["view_workflows", "create_workflow"]);
|
||||
const router = useRouter();
|
||||
|
||||
/* const allowedEntities = useAllowedEntities(user, userEntitiesWithLabel, "view_workflows");
|
||||
const allowedSomeEntities = useAllowedEntitiesSomePermissions(user, userEntitiesWithLabel, ["view_workflows", "create_workflow"]); */
|
||||
|
||||
const ENTITY_OPTIONS = [
|
||||
...userEntitiesWithLabel
|
||||
@@ -157,10 +170,8 @@ export default function ApprovalWorkflows({user, initialWorkflows, workflowsAssi
|
||||
reload();
|
||||
})
|
||||
.catch((reason) => {
|
||||
if (reason.response.status === 404) {
|
||||
toast.error("Approval Workflow not found!");
|
||||
} else if (reason.response.status === 403) {
|
||||
toast.error("You do not have permission to delete an Approval Workflow!");
|
||||
if (reason.response.status === 403) {
|
||||
toast.error("You do not have permission to delete this Approval Workflow!");
|
||||
} else {
|
||||
toast.error("Something went wrong, please try again later.");
|
||||
}
|
||||
@@ -249,7 +260,7 @@ export default function ApprovalWorkflows({user, initialWorkflows, workflowsAssi
|
||||
columnHelper.accessor("steps", {
|
||||
header: "ACTIONS",
|
||||
id: "actions",
|
||||
cell: ({row}) => {
|
||||
cell: ({ row }) => {
|
||||
const steps = row.original.steps;
|
||||
const currentStep = steps.find((step) => !step.completed);
|
||||
const rejected = steps.find((step) => step.rejected);
|
||||
@@ -259,6 +270,7 @@ export default function ApprovalWorkflows({user, initialWorkflows, workflowsAssi
|
||||
<button
|
||||
data-tip="Delete"
|
||||
className="cursor-pointer tooltip"
|
||||
disabled={!doesEntityAllow(user, userEntitiesWithLabel.find(entity => entity.id === row.original.entityId)!, "delete_workflow")}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
deleteApprovalWorkflow(row.original._id?.toString(), row.original.name);
|
||||
@@ -267,13 +279,16 @@ export default function ApprovalWorkflows({user, initialWorkflows, workflowsAssi
|
||||
</button>
|
||||
|
||||
{currentStep && !rejected && (
|
||||
<Link
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
<button
|
||||
data-tip="Edit"
|
||||
href={`/approval-workflows/${row.original._id?.toString()}/edit`}
|
||||
className="cursor-pointer tooltip">
|
||||
className="cursor-pointer tooltip"
|
||||
disabled={!doesEntityAllow(user, userEntitiesWithLabel.find(entity => entity.id === row.original.entityId)!, "edit_workflow")}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
router.push(`/approval-workflows/${row.original._id?.toString()}/edit`);
|
||||
}}>
|
||||
<FaRegEdit className="hover:text-mti-purple-light transition ease-in-out duration-300" />
|
||||
</Link>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -340,7 +355,7 @@ export default function ApprovalWorkflows({user, initialWorkflows, workflowsAssi
|
||||
<Tip text="An exam submission will instantiate the approval workflow configured for the exam author. The exam will be valid only when all the steps of the workflow have been approved."></Tip>
|
||||
|
||||
<div className="px-6 pb-4 bg-mti-purple-ultralight rounded-2xl border-2 border-mti-purple-light border-opacity-40">
|
||||
<table className="w-full table-auto border-separate border-spacing-y-2" style={{tableLayout: "auto"}}>
|
||||
<table className="w-full table-auto border-separate border-spacing-y-2" style={{ tableLayout: "auto" }}>
|
||||
<thead>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
<tr key={headerGroup.id}>
|
||||
@@ -357,7 +372,7 @@ export default function ApprovalWorkflows({user, initialWorkflows, workflowsAssi
|
||||
<tr
|
||||
key={row.id}
|
||||
onClick={() => (window.location.href = `/approval-workflows/${row.original._id?.toString()}`)}
|
||||
style={{cursor: "pointer"}}
|
||||
style={{ cursor: "pointer" }}
|
||||
className="bg-purple-50">
|
||||
{row.getVisibleCells().map((cell, cellIndex) => {
|
||||
const lastCellIndex = row.getVisibleCells().length - 1;
|
||||
|
||||
Reference in New Issue
Block a user